From 7855186b608676741356d1618cc3c688faff6d31 Mon Sep 17 00:00:00 2001 From: Sandeep Kunta Date: Wed, 9 Oct 2013 00:57:26 +0530 Subject: STK Call Control feature implementation. Support to display the exact call failure reason for STK cc. Change-Id: I1076f2b3ba93e6f96e85efec32b11250bbfd7f6f (cherry picked from commit af7567c997a072d89838793000bdb8b107be0ed4) (cherry picked from commit 2633093e75f5c53c2a3826592530e110d52bf5e2) (cherry picked from commit 6dc03b1c11dbbfd1c175724e8646543bb9c5d55e) --- src/com/android/incallui/InCallActivity.java | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index 58db132a..66e68308 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -475,6 +475,12 @@ public class InCallActivity extends Activity { resId = R.string.callFailed_dsac_restricted_emergency; } else if (cause == Call.DisconnectCause.CS_RESTRICTED_NORMAL) { resId = R.string.callFailed_dsac_restricted_normal; + } else if (cause == Call.DisconnectCause.DIAL_MODIFIED_TO_USSD) { + resId = R.string.callFailed_dialToUssd; + } else if (cause == Call.DisconnectCause.DIAL_MODIFIED_TO_SS) { + resId = R.string.callFailed_dialToSs; + } else if (cause == Call.DisconnectCause.DIAL_MODIFIED_TO_DIAL) { + resId = R.string.callFailed_dialToDial; } return resId; -- cgit v1.2.3 From 9bfd2d7cac8b98d476b20903d56912e7eb3c6950 Mon Sep 17 00:00:00 2001 From: Muhammed Siju Date: Thu, 10 Oct 2013 11:00:19 +0530 Subject: Display supplementary service dialogs for CF and CB. When call is not answered and is forwarded, display "Call Unanswered and Forwarded" message at MT side. When Call Barring is enabled on MT side, display "Party has barred all Incoming calls" message to the user on MO side. Change-Id: I4ae0b8b1dd2588b6cee96bc1c2d296290810c11c (cherry picked from commit e7e81e031e526a07875f17216b217b672b0a7044) (cherry picked from commit de1ecaecce46a3bac5581927eaf1587390e9b24e) --- src/com/android/incallui/InCallActivity.java | 34 +++++++++++++++++++++------ src/com/android/incallui/InCallPresenter.java | 2 +- 2 files changed, 28 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index 66e68308..60410bd3 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -416,11 +416,12 @@ public class InCallActivity extends Activity { return super.dispatchPopulateAccessibilityEvent(event); } - public void maybeShowErrorDialogOnDisconnect(Call.DisconnectCause cause) { - Log.d(this, "maybeShowErrorDialogOnDisconnect"); + public void maybeShowErrorDialogOnDisconnect(Call call) { + Log.d(this, "maybeShowErrorDialogOnDisconnect: Call=" + call); - if (!isFinishing()) { - final int resId = getResIdForDisconnectCause(cause); + if (!isFinishing() && call != null) { + final int resId = getResIdForDisconnectCause(call.getDisconnectCause(), + call.getSuppServNotification()); if (resId != INVALID_RES_ID) { showErrorDialog(resId); } @@ -462,11 +463,30 @@ public class InCallActivity extends Activity { mDialog.show(); } - private int getResIdForDisconnectCause(Call.DisconnectCause cause) { + private int getResIdForDisconnectCause(Call.DisconnectCause cause, + Call.SsNotification notification) { int resId = INVALID_RES_ID; - if (cause == Call.DisconnectCause.CALL_BARRED) { - resId = R.string.callFailed_cb_enabled; + if (cause == Call.DisconnectCause.INCOMING_MISSED) { + // If the network sends SVC Notification then this dialog will be displayed + // in case of B when the incoming call at B is not answered and gets forwarded + // to C + if (notification != null && notification.notificationType == 1 && + notification.code == + Call.SsNotification.MT_CODE_ADDITIONAL_CALL_FORWARDED) { + resId = R.string.callUnanswered_forwarded; + } + } else if (cause == Call.DisconnectCause.CALL_BARRED) { + // When call is disconnected with this code then it can either be barring from + // MO side or MT side. + // In MT case, if network sends SVC Notification then this dialog will be + // displayed when A is calling B & incoming is barred on B. + if (notification != null && notification.notificationType == 0 && + notification.code == Call.SsNotification.MO_CODE_INCOMING_CALLS_BARRED) { + resId = R.string.callFailed_incoming_cb_enabled; + } else { + resId = R.string.callFailed_cb_enabled; + } } else if (cause == Call.DisconnectCause.FDN_BLOCKED) { resId = R.string.callFailed_fdn_only; } else if (cause == Call.DisconnectCause.CS_RESTRICTED) { diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 5830e726..57528d54 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -508,7 +508,7 @@ public class InCallPresenter implements CallList.Listener { private void maybeShowErrorDialogOnDisconnect(Call call) { // For newly disconnected calls, we may want to show a dialog on specific error conditions if (isActivityStarted() && call.getState() == Call.State.DISCONNECTED) { - mInCallActivity.maybeShowErrorDialogOnDisconnect(call.getDisconnectCause()); + mInCallActivity.maybeShowErrorDialogOnDisconnect(call); } } -- cgit v1.2.3 From 0efaf169ec75da26ac1390e0c84ebc4671b98a2c Mon Sep 17 00:00:00 2001 From: Sandeep Gutta Date: Fri, 18 Oct 2013 17:56:51 +0530 Subject: MSIM: Add multisim support. Add support to display subscription information on UI for multi sim targets. Change-Id: Ic2ac06f727bb913f72138730a0f3c6aca0798320 (cherry picked from commit 1d60de42aaddb7dd7c3e1d9e5a3eee55091aa7f4) (cherry picked from commit 2a1003f3a9b38938882cb9745238a3e31adacbb0) --- src/com/android/incallui/CallCardFragment.java | 22 ++++++++++++++++++ src/com/android/incallui/CallCardPresenter.java | 4 ++++ src/com/android/incallui/CallCommandClient.java | 29 ++++++++++++++++++++++++ src/com/android/incallui/CallHandlerService.java | 12 ++++++++++ src/com/android/incallui/CallList.java | 14 ++++++++++++ 5 files changed, 81 insertions(+) (limited to 'src') diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index 0d26b82c..845f798f 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -22,6 +22,7 @@ import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.telephony.MSimTelephonyManager; import android.text.TextUtils; import android.view.Gravity; import android.view.LayoutInflater; @@ -54,6 +55,7 @@ public class CallCardFragment extends BaseFragment void setPrimaryPhoneNumber(String phoneNumber); void setPrimaryLabel(String label); } + + public int getActiveSubscription() { + return CallCommandClient.getInstance().getActiveSubscription(); + } } diff --git a/src/com/android/incallui/CallCommandClient.java b/src/com/android/incallui/CallCommandClient.java index 52d2100c..1750a385 100644 --- a/src/com/android/incallui/CallCommandClient.java +++ b/src/com/android/incallui/CallCommandClient.java @@ -18,6 +18,7 @@ package com.android.incallui; import android.os.RemoteException; +import com.android.internal.telephony.MSimConstants; import com.android.services.telephony.common.AudioMode; import com.android.services.telephony.common.ICallCommandService; @@ -243,4 +244,32 @@ public class CallCommandClient { } } + public void setActiveSubscription(int subscriptionId) { + Log.i(this, "set active sub = " + subscriptionId); + if (mCommandService == null) { + Log.e(this, "Cannot set active Sub; CallCommandService == null"); + return; + } + try { + mCommandService.setActiveSubscription(subscriptionId); + } catch (RemoteException e) { + Log.e(this, "Error setActiveSub.", e); + } + } + + public int getActiveSubscription() { + int subscriptionId = MSimConstants.INVALID_SUBSCRIPTION; + + if (mCommandService == null) { + Log.e(this, "Cannot get active sub; CallCommandService == null"); + return subscriptionId; + } + try { + subscriptionId = mCommandService.getActiveSubscription(); + } catch (RemoteException e) { + Log.e(this, "Error getActiveSub.", e); + } + Log.i(this, "get active sub " + subscriptionId); + return subscriptionId; + } } diff --git a/src/com/android/incallui/CallHandlerService.java b/src/com/android/incallui/CallHandlerService.java index 06b10ab2..f872dd40 100644 --- a/src/com/android/incallui/CallHandlerService.java +++ b/src/com/android/incallui/CallHandlerService.java @@ -48,6 +48,7 @@ public class CallHandlerService extends Service { private static final int ON_POST_CHAR_WAIT = 8; private static final int ON_START = 9; private static final int ON_DESTROY = 10; + private static final int ON_ACTIVE_SUB_CHANGE = 11; private static final int LARGEST_MSG_ID = ON_DESTROY; @@ -184,6 +185,12 @@ public class CallHandlerService extends Service { mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_POST_CHAR_WAIT, callId, 0, chars)); } + + @Override + public void onActiveSubChanged(int activeSub) { + mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_ACTIVE_SUB_CHANGE, activeSub)); + } + }; private void doStart(ICallCommandService service) { @@ -302,6 +309,11 @@ public class CallHandlerService extends Service { case ON_DESTROY: doStop(); break; + case ON_ACTIVE_SUB_CHANGE: + Log.i(TAG, "ON_ACTIVE_SUB_CHANGE: " + msg.obj); + mCallList.onActiveSubChanged((Integer) msg.obj); + break; + default: break; } diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java index ba123b0c..a6aef7e5 100644 --- a/src/com/android/incallui/CallList.java +++ b/src/com/android/incallui/CallList.java @@ -53,6 +53,7 @@ public class CallList { private final HashMap> mCallUpdateListenerMap = Maps .newHashMap(); + private int mSubscription = 0; /** * Static singleton accessor method. @@ -474,4 +475,17 @@ public class CallList { // TODO: refactor and limit arg to be call state. Caller info is not needed. public void onCallStateChanged(Call call); } + + /** + * Called when active subscription changes. + */ + public void onActiveSubChanged(int activeSub) { + Log.d(this, "onActiveSubChanged: old = " + mSubscription + " new = " + activeSub); + + mSubscription = activeSub; + } + + public int getActiveSubscription() { + return mSubscription; + } } -- cgit v1.2.3 From e710212acbb403460f24a9b87eeec10fb77b41cc Mon Sep 17 00:00:00 2001 From: Pavan Tatavarthi Date: Wed, 16 Oct 2013 18:15:26 -0700 Subject: IMS support for UI interface 1. UI support for conference call participant display, hangup using uri, conference uri dial, add participant, 2. Add ims related data types to Call object 3. Add ims related APIs to CallCommandService and CallHandlerService Change-Id: If782aa90dc4741499a67ee6a62f48576425a0574 --- src/com/android/incallui/CallButtonFragment.java | 11 ++++ src/com/android/incallui/CallButtonPresenter.java | 8 +++ src/com/android/incallui/CallCommandClient.java | 28 ++++++++++ src/com/android/incallui/CallHandlerService.java | 2 + .../incallui/ConferenceManagerFragment.java | 11 ++++ .../incallui/ConferenceManagerPresenter.java | 60 +++++++++++++++++++--- src/com/android/incallui/InCallApp.java | 2 + src/com/android/incallui/InCallPresenter.java | 22 ++++++++ 8 files changed, 138 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallButtonFragment.java b/src/com/android/incallui/CallButtonFragment.java index ed769033..b96cbd66 100644 --- a/src/com/android/incallui/CallButtonFragment.java +++ b/src/com/android/incallui/CallButtonFragment.java @@ -48,6 +48,7 @@ public class CallButtonFragment private ImageButton mMergeButton; private ImageButton mAddCallButton; private ImageButton mSwapButton; + private ImageButton mAddParticipantButton; private PopupMenu mAudioModePopup; private boolean mAudioModePopupVisible; @@ -141,6 +142,8 @@ public class CallButtonFragment mMergeButton.setOnClickListener(this); mSwapButton = (ImageButton) parent.findViewById(R.id.swapButton); mSwapButton.setOnClickListener(this); + mAddParticipantButton = (ImageButton) parent.findViewById(R.id.addParticipant); + mAddParticipantButton.setOnClickListener(this); return parent; } @@ -183,6 +186,9 @@ public class CallButtonFragment case R.id.dialpadButton: getPresenter().showDialpadClicked(mShowDialpadButton.isChecked()); break; + case R.id.addParticipant: + getPresenter().addParticipantClicked(); + break; default: Log.wtf(this, "onClick: unexpected"); break; @@ -207,6 +213,7 @@ public class CallButtonFragment mMergeButton.setEnabled(isEnabled); mAddCallButton.setEnabled(isEnabled); mSwapButton.setEnabled(isEnabled); + mAddParticipantButton.setEnabled(isEnabled); } @Override @@ -254,6 +261,10 @@ public class CallButtonFragment mAddCallButton.setEnabled(enabled); } + public void enableAddParticipant(boolean show) { + mAddParticipantButton.setVisibility(show ? View.VISIBLE : View.GONE); + } + @Override public void setAudio(int mode) { updateAudioButtons(getPresenter().getSupportedAudio()); diff --git a/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java index be257378..9f909587 100644 --- a/src/com/android/incallui/CallButtonPresenter.java +++ b/src/com/android/incallui/CallButtonPresenter.java @@ -22,6 +22,7 @@ import com.android.incallui.InCallPresenter.InCallStateListener; import com.android.incallui.InCallPresenter.IncomingCallListener; import com.android.services.telephony.common.AudioMode; import com.android.services.telephony.common.Call; +import com.android.services.telephony.common.CallDetails; import com.android.services.telephony.common.Call.Capabilities; import android.telephony.PhoneNumberUtils; @@ -190,6 +191,10 @@ public class CallButtonPresenter extends Presenter 0) { + mParticipantList = confParticipantList; + mNumCallersInConference = mParticipantList.length; + return; + } + } mCallerIds = callList.getActiveOrBackgroundCall().getChildCallIds().toArray(new Integer[0]); mNumCallersInConference = mCallerIds.length; + } + + private void update(CallList callList) { + mCallerIds = null; + // set mNumCallersInConference and mParticipantList + initParticipantList(callList); + Log.v(this, "Number of calls is " + String.valueOf(mNumCallersInConference)); // Users can split out a call from the conference call if there either the active call @@ -95,10 +122,13 @@ public class ConferenceManagerPresenter for (int i = 0; i < MAX_CALLERS_IN_CONFERENCE; i++) { if (i < mNumCallersInConference) { // Fill in the row in the UI for this caller. - - final ContactCacheEntry contactCache = ContactInfoCache.getInstance(mContext). - getInfo(mCallerIds[i]); - updateManageConferenceRow(i, contactCache, canSeparate); + if (mParticipantList == null) { + final ContactCacheEntry contactCache = ContactInfoCache.getInstance(mContext). + getInfo(mCallerIds[i]); + updateManageConferenceRow(i, contactCache, canSeparate); + } else { + updateManageConferenceRow(i, mParticipantList[i]); + } } else { // Blank out this row in the UI updateManageConferenceRow(i, null, false); @@ -140,6 +170,17 @@ public class ConferenceManagerPresenter } } + public void updateManageConferenceRow(final int i, final String url) { + if (url != null) { + getUi().setRowVisible(i, true); + getUi().setupEndButtonForRowWithUrl(i, url); + getUi().displayCallerInfoForConferenceRow(i, "", url, ""); + } else { + // Disable this row of the Manage conference panel: + getUi().setRowVisible(i, false); + } + } + public void manageConferenceDoneClicked() { getUi().setVisible(false); } @@ -156,6 +197,11 @@ public class ConferenceManagerPresenter CallCommandClient.getInstance().disconnectCall(mCallerIds[rowId]); } + public void endConferenceConnectionUrl(int rowId , String url) { + CallCommandClient.getInstance().hangupWithReason(-1, url, + true, Call.DisconnectCause.NORMAL.ordinal(), ""); + } + public interface ConferenceManagerUi extends Ui { void setVisible(boolean on); boolean isFragmentVisible(); @@ -164,6 +210,8 @@ public class ConferenceManagerPresenter String callerNumberType); void setCanSeparateButtonForRow(int rowId, boolean canSeparate); void setupEndButtonForRow(int rowId); + + void setupEndButtonForRowWithUrl(int rowId, String url); void startConferenceTime(long base); void stopConferenceTime(); } diff --git a/src/com/android/incallui/InCallApp.java b/src/com/android/incallui/InCallApp.java index 7d276bce..ea7b1381 100644 --- a/src/com/android/incallui/InCallApp.java +++ b/src/com/android/incallui/InCallApp.java @@ -34,6 +34,8 @@ public class InCallApp extends Application { */ public static final String ACTION_HANG_UP_ONGOING_CALL = "com.android.incallui.ACTION_HANG_UP_ONGOING_CALL"; + public static final String ADD_CALL_MODE_KEY = "add_call_mode"; + public static final String ADD_PARTICIPANT_KEY = "add_participant"; public InCallApp() { } diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 57528d54..1acbf196 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -22,6 +22,7 @@ import com.google.common.base.Preconditions; import android.content.Context; import android.content.Intent; +import android.content.ActivityNotFoundException; import com.android.services.telephony.common.Call; import com.android.services.telephony.common.Call.Capabilities; @@ -55,6 +56,7 @@ public class InCallPresenter implements CallList.Listener { private InCallState mInCallState = InCallState.NO_CALLS; private ProximitySensor mProximitySensor; private boolean mServiceConnected = false; + private static String LOG_TAG = "InCallPresenter"; /** * Is true when the activity has been previously started. Some code needs to know not just if @@ -688,6 +690,26 @@ public class InCallPresenter implements CallList.Listener { return intent; } + public void sendAddParticipantIntent() { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // when we request the dialer come up, we also want to inform + // it that we're going through the "add participant" option from the + // InCallScreen. + intent.putExtra(InCallApp.ADD_CALL_MODE_KEY, true); + intent.putExtra(InCallApp.ADD_PARTICIPANT_KEY, true); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + // This is rather rare but possible. + // Note: this method is used even when the phone is encrypted. At + // that moment + // the system may not find any Activity which can accept this Intent + Log.e(LOG_TAG, "Activity for adding calls isn't found."); + } + } + /** * Private constructor. Must use getInstance() to get this singleton. */ -- cgit v1.2.3 From f5bc563acbfd200fa445e39c37492b000fdb8417 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Thu, 17 Oct 2013 15:24:23 -0700 Subject: Phone: Add support for video calls Added support for video calls in InCallUI. - Video Call UI - IMS Media - UI support for Upgrade/Downgrade Calls Change-Id: I96e17dfe3cc6ae7afb5b43c86538bb62fe31a03b --- src/com/android/incallui/AnswerFragment.java | 30 +- src/com/android/incallui/AnswerPresenter.java | 17 +- src/com/android/incallui/CallButtonFragment.java | 30 + src/com/android/incallui/CallButtonPresenter.java | 19 + src/com/android/incallui/CallCardFragment.java | 190 ++++++- src/com/android/incallui/CallCardPresenter.java | 29 +- src/com/android/incallui/CallUtils.java | 111 ++++ src/com/android/incallui/CameraHandler.java | 403 ++++++++++++++ src/com/android/incallui/GlowPadWrapper.java | 34 +- src/com/android/incallui/InCallActivity.java | 151 +++++ src/com/android/incallui/InCallPresenter.java | 128 +++++ src/com/android/incallui/MediaHandler.java | 228 ++++++++ src/com/android/incallui/VideoCallManager.java | 309 +++++++++++ src/com/android/incallui/VideoCallPanel.java | 641 ++++++++++++++++++++++ src/com/android/incallui/ZoomControl.java | 124 +++++ src/com/android/incallui/ZoomControlBar.java | 152 +++++ 16 files changed, 2576 insertions(+), 20 deletions(-) create mode 100644 src/com/android/incallui/CallUtils.java create mode 100644 src/com/android/incallui/CameraHandler.java create mode 100644 src/com/android/incallui/MediaHandler.java create mode 100644 src/com/android/incallui/VideoCallManager.java create mode 100644 src/com/android/incallui/VideoCallPanel.java create mode 100644 src/com/android/incallui/ZoomControl.java create mode 100644 src/com/android/incallui/ZoomControlBar.java (limited to 'src') diff --git a/src/com/android/incallui/AnswerFragment.java b/src/com/android/incallui/AnswerFragment.java index a53d8c9e..c6a2d9bf 100644 --- a/src/com/android/incallui/AnswerFragment.java +++ b/src/com/android/incallui/AnswerFragment.java @@ -1,4 +1,8 @@ /* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -135,6 +139,28 @@ public class AnswerFragment extends BaseFragment call.getCallId()); getUi().showAnswerUi(true); - if (call.can(Call.Capabilities.RESPOND_VIA_TEXT) && textMsgs != null) { + if(CallUtils.isVideoCall(call)) { + getUi().showVideoButtons(); + if (textMsgs != null) + getUi().configureMessageDialog(textMsgs); + } else if (call.can(Call.Capabilities.RESPOND_VIA_TEXT) && textMsgs != null) { getUi().showTextButton(true); getUi().configureMessageDialog(textMsgs); } else { @@ -118,14 +126,14 @@ public class AnswerPresenter extends Presenter } } - public void onAnswer() { + public void onAnswer(int callType) { if (mCallId == Call.INVALID_CALL_ID) { return; } - Log.d(this, "onAnswer " + mCallId); + Log.d(this, "onAnswer: callId=" + mCallId + "callType=" + callType); - CallCommandClient.getInstance().answerCall(mCallId); + CallCommandClient.getInstance().answerCallWithCallType(mCallId, callType); } public void onDecline() { @@ -154,6 +162,7 @@ public class AnswerPresenter extends Presenter interface AnswerUi extends Ui { public void showAnswerUi(boolean show); + public void showVideoButtons(); public void showTextButton(boolean show); public void showMessageDialog(); public void configureMessageDialog(ArrayList textResponses); diff --git a/src/com/android/incallui/CallButtonFragment.java b/src/com/android/incallui/CallButtonFragment.java index b96cbd66..b1c2419c 100644 --- a/src/com/android/incallui/CallButtonFragment.java +++ b/src/com/android/incallui/CallButtonFragment.java @@ -1,4 +1,8 @@ /* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +28,7 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.widget.Button; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.PopupMenu; @@ -57,6 +62,8 @@ public class CallButtonFragment private View mManageConferenceButton; private View mGenericMergeButton; + private Button mModifyCallButton; + @Override CallButtonPresenter createPresenter() { // TODO: find a cleaner way to include audio mode provider than @@ -145,6 +152,9 @@ public class CallButtonFragment mAddParticipantButton = (ImageButton) parent.findViewById(R.id.addParticipant); mAddParticipantButton.setOnClickListener(this); + mModifyCallButton = (Button) parent.findViewById(R.id.modifyCallButton); + mModifyCallButton.setOnClickListener(this); + return parent; } @@ -189,6 +199,9 @@ public class CallButtonFragment case R.id.addParticipant: getPresenter().addParticipantClicked(); break; + case R.id.modifyCallButton: + getPresenter().modifyCallButtonClicked(); + break; default: Log.wtf(this, "onClick: unexpected"); break; @@ -309,6 +322,23 @@ public class CallButtonFragment return true; } + @Override + public void displayModifyCallOptions(int callId) { + if (getActivity() != null && getActivity() instanceof InCallActivity) { + ((InCallActivity) getActivity()).displayModifyCallOptions(callId); + } + } + + @Override + public void enableModifyCall(boolean enabled) { + mModifyCallButton.setEnabled(enabled); + } + + @Override + public void showModifyCall(boolean show) { + mModifyCallButton.setVisibility(show ? View.VISIBLE : View.GONE); + } + // PopupMenu.OnDismissListener implementation; see showAudioModePopup(). // This gets called when the PopupMenu gets dismissed for *any* reason, like // the user tapping outside its bounds, or pressing Back, or selecting one diff --git a/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java index 9f909587..1f0ee4d9 100644 --- a/src/com/android/incallui/CallButtonPresenter.java +++ b/src/com/android/incallui/CallButtonPresenter.java @@ -1,4 +1,8 @@ /* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -215,6 +219,13 @@ public class CallButtonPresenter extends PresenterVT call type change happens. + */ + log("initVideoCall mAudioDeviceInitialized: " + mAudioDeviceInitialized); + if (!mAudioDeviceInitialized ) { + switchInVideoCallAudio(); // Set audio to speaker by default + mAudioDeviceInitialized = true; + } + // Choose camera direction based on call type + mVideoCallPanel.onCallInitiating(callType); + } + + /** + * Switches the current routing of in-call audio for the video call + */ + private void switchInVideoCallAudio() { + Log.d(this,"In switchInVideoCallAudio"); + + // If the wired headset is connected then the AudioService takes care of + // routing audio to the headset + int mode = AudioModeProvider.getInstance().getAudioMode(); + CallCommandClient.getInstance().setAudioMode(mode); + if (mode == AudioMode.WIRED_HEADSET) { + Log.d(this,"Wired headset connected, not routing audio to speaker"); + return; + } + + // If the bluetooth is available then BluetoothHandsfree class takes + // care of making sure that the audio is routed to Bluetooth by default. + // However if the audio is not connected to Bluetooth because user wanted + // audio off then continue to turn on the speaker + if (mode == AudioMode.BLUETOOTH ) { + Log.d(this, "Bluetooth connected, not routing audio to speaker"); + return; + } + + // If the speaker is explicitly disabled then do not enable it. + if (SystemProperties.getInt(PROPERTY_IMS_AUDIO_OUTPUT, + IMS_AUDIO_OUTPUT_DEFAULT) == IMS_AUDIO_OUTPUT_DISABLE_SPEAKER) { + Log.d(this, "Speaker disabled, not routing audio to speaker"); + return; + } + + // If the bluetooth headset or the wired headset is not connected and + // the speaker is not disabled then turn on speaker by default + // for the VT call + CallCommandClient.getInstance().setAudioMode(AudioMode.SPEAKER); + } + + /** + * Return true if mPhoto is available and is visible + * + * @return + */ + private boolean isPhotoVisible() { + return ((mPhoto != null) && (mPhoto.getVisibility() == View.VISIBLE)); + } + + private void log(String msg) { + Log.d(this, msg); + } + + private void loge(String msg) { + Log.e(this, msg); + } } diff --git a/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java index a83f0e38..7c35cc4d 100644 --- a/src/com/android/incallui/CallCardPresenter.java +++ b/src/com/android/incallui/CallCardPresenter.java @@ -1,4 +1,8 @@ /* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +25,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.graphics.Bitmap; +import android.provider.MediaStore.Audio; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.text.format.DateUtils; @@ -36,6 +41,7 @@ import com.android.services.telephony.common.Call; import com.android.services.telephony.common.Call.Capabilities; import com.android.services.telephony.common.CallIdentification; import com.google.common.base.Preconditions; +import com.android.incallui.CallUtils; /** * Presenter for the Call Card Fragment. @@ -185,13 +191,16 @@ public class CallCardPresenter extends Presenter } // Set the call state + final int callType = CallUtils.getCallType(mPrimary); + if (mPrimary != null) { final boolean bluetoothOn = (AudioModeProvider.getInstance().getAudioMode() == AudioMode.BLUETOOTH); ui.setCallState(mPrimary.getState(), mPrimary.getDisconnectCause(), bluetoothOn, - getGatewayLabel(), getGatewayNumber()); + getGatewayLabel(), getGatewayNumber(), callType); } else { - ui.setCallState(Call.State.IDLE, Call.DisconnectCause.UNKNOWN, false, null, null); + ui.setCallState(Call.State.IDLE, + Call.DisconnectCause.UNKNOWN, false, null, null,callType); } } @@ -201,7 +210,7 @@ public class CallCardPresenter extends Presenter final boolean bluetoothOn = (AudioMode.BLUETOOTH == mode); getUi().setCallState(mPrimary.getState(), mPrimary.getDisconnectCause(), bluetoothOn, - getGatewayLabel(), getGatewayNumber()); + getGatewayLabel(), getGatewayNumber(), CallUtils.getCallType(mPrimary)); } } @@ -272,7 +281,8 @@ public class CallCardPresenter extends Presenter return; } if (entry.photo != null) { - if (mPrimary != null && callId == mPrimary.getCallId()) { + if (mPrimary != null && !CallUtils.isVideoCall(mPrimary) && + callId == mPrimary.getCallId()) { getUi().setPrimaryImage(entry.photo); } else if (mSecondary != null && callId == mSecondary.getCallId()) { getUi().setSecondaryImage(entry.photo); @@ -354,14 +364,16 @@ public class CallCardPresenter extends Presenter } final boolean isGenericConf = isGenericConference(mPrimary); + final boolean isVideo = CallUtils.isVideoCall(mPrimary); if (entry != null) { final String name = getNameForCall(entry); final String number = getNumberForCall(entry); final boolean nameIsNumber = name != null && name.equals(entry.number); ui.setPrimary(number, name, nameIsNumber, entry.label, - entry.photo, isConference, isGenericConf, entry.isSipCall); + entry.photo, isConference, isGenericConf, entry.isSipCall, isVideo); } else { - ui.setPrimary(null, null, false, null, null, isConference, isGenericConf, false); + ui.setPrimary(null, null, false, null, null, + isConference, isGenericConf, false, isVideo); } } @@ -458,12 +470,13 @@ public class CallCardPresenter extends Presenter public interface CallCardUi extends Ui { void setVisible(boolean on); void setPrimary(String number, String name, boolean nameIsNumber, String label, - Drawable photo, boolean isConference, boolean isGeneric, boolean isSipCall); + Drawable photo, boolean isConference, boolean isGeneric, boolean isSipCall, + boolean isVideo); void setSecondary(boolean show, String name, boolean nameIsNumber, String label, Drawable photo, boolean isConference, boolean isGeneric); void setSecondaryImage(Drawable image); void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn, - String gatewayLabel, String gatewayNumber); + String gatewayLabel, String gatewayNumber, int callType); void setPrimaryCallElapsedTime(boolean show, String duration); void setPrimaryName(String name, boolean nameIsNumber); void setPrimaryImage(Drawable image); diff --git a/src/com/android/incallui/CallUtils.java b/src/com/android/incallui/CallUtils.java new file mode 100644 index 00000000..0afcfcd3 --- /dev/null +++ b/src/com/android/incallui/CallUtils.java @@ -0,0 +1,111 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import com.android.services.telephony.common.Call; +import com.android.services.telephony.common.CallDetails; +import com.google.common.base.Preconditions; + +public class CallUtils { + + public static boolean isVideoCall(int callType) { + return callType == CallDetails.CALL_TYPE_VT || + callType == CallDetails.CALL_TYPE_VT_TX || + callType == CallDetails.CALL_TYPE_VT_RX; + } + + public static int getCallType(Call call) { + final CallDetails cd = getCallDetails(call); + return cd != null ? cd.getCallType() : CallDetails.CALL_TYPE_UNKNOWN; + } + + public static int getProposedCallType(Call call) { + final CallDetails cd = getCallModifyDetails(call); + return cd != null ? cd.getCallType() : CallDetails.CALL_TYPE_UNKNOWN; + } + + public static boolean hasCallModifyFailed(Call call) { + final CallDetails cd = getCallModifyDetails(call); + return cd != null && cd.getErrorInfo() != null + && (Integer.parseInt(cd.getErrorInfo()) == 0); + + } + + private static CallDetails getCallDetails(Call call) { + return call != null ? call.getCallDetails() : null; + } + + private static CallDetails getCallModifyDetails(Call call) { + return call != null ? call.getCallModifyDetails() : null; + } + + public static boolean isVideoCall(Call call) { + Preconditions.checkNotNull(call); + Preconditions.checkNotNull(call.getCallDetails()); + return isVideoCall(call.getCallDetails().getCallType()); + } + + public static String fromCallType(int callType) { + String str = ""; + switch (callType) { + case CallDetails.CALL_TYPE_VT: + str = "VT"; + break; + case CallDetails.CALL_TYPE_VT_TX: + str = "VT_TX"; + break; + case CallDetails.CALL_TYPE_VT_RX: + str = "VT_RX"; + break; + } + return str; + } + + public static boolean isImsCall(Call call) { + if (call == null) return false; + Preconditions.checkNotNull(call.getCallDetails()); + final int callType = call.getCallDetails().getCallType(); + final boolean isImsVideoCall = isVideoCall(call) || + (callType == CallDetails.CALL_TYPE_VT_NODIR); + final boolean isImsVoiceCall = (callType == CallDetails.CALL_TYPE_VOICE + && call.getCallDetails().getCallDomain() == CallDetails.CALL_DOMAIN_PS); + return isImsVideoCall || isImsVoiceCall; + } + + public static boolean hasImsCall(CallList callList) { + Preconditions.checkNotNull(callList); + return isImsCall(callList.getIncomingCall()) + || isImsCall(callList.getOutgoingCall()) + || isImsCall(callList.getActiveCall()) + || isImsCall(callList.getBackgroundCall()) + || isImsCall(callList.getDisconnectingCall()) + || isImsCall(callList.getDisconnectedCall()); + } + +} diff --git a/src/com/android/incallui/CameraHandler.java b/src/com/android/incallui/CameraHandler.java new file mode 100644 index 00000000..b40c8dd5 --- /dev/null +++ b/src/com/android/incallui/CameraHandler.java @@ -0,0 +1,403 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.util.Log; +import android.view.Surface; +import android.view.TextureView; +import android.view.WindowManager; + +import java.io.IOException; +import java.util.List; + +/** + * The class is used to hold an {@code android.hardware.Camera} instance. + *

+ * The {@code open()} and {@code release()} calls are similar to the ones in + * {@code android.hardware.Camera}. + */ + +public class CameraHandler implements Camera.PreviewCallback{ + public static final int CAMERA_UNKNOWN = -1; + private static final String TAG = "VideoCallCameraHandler"; + private static final boolean DBG = true; + private android.hardware.Camera mCameraDevice; + private int mNumberOfCameras; + private int mCameraId = CAMERA_UNKNOWN; // current camera id + private int mBackCameraId = CAMERA_UNKNOWN, mFrontCameraId = CAMERA_UNKNOWN; + private CameraInfo[] mInfo; + private CameraState mCameraState = CameraState.CAMERA_CLOSED; + private Parameters mParameters; + private Context mContext; + + // Use a singleton. + private static CameraHandler mInstance; + + /** + * Enum that defines the various camera states + */ + public enum CameraState { + CAMERA_CLOSED, // Camera is not yet opened or is closed + PREVIEW_STOPPED, // Camera is open and preview not started + PREVIEW_STARTED, // Preview is active + }; + + /** + * This method returns the single instance of CameraManager object + * @param mContext + */ + public static synchronized CameraHandler getInstance(Context context) { + if (mInstance == null) { + mInstance = new CameraHandler(context); + } + return mInstance; + } + + /** + * Private constructor for CameraManager + * @param mContext + */ + private CameraHandler(Context context) { + mContext = context; + mNumberOfCameras = android.hardware.Camera.getNumberOfCameras(); + log("Number of cameras supported is: " + mNumberOfCameras); + mInfo = new CameraInfo[mNumberOfCameras]; + for (int i = 0; i < mNumberOfCameras; i++) { + mInfo[i] = new CameraInfo(); + android.hardware.Camera.getCameraInfo(i, mInfo[i]); + if (mBackCameraId == CAMERA_UNKNOWN + && mInfo[i].facing == CameraInfo.CAMERA_FACING_BACK) { + mBackCameraId = i; + log("Back camera ID is: " + mBackCameraId); + } + if (mFrontCameraId == CAMERA_UNKNOWN + && mInfo[i].facing == CameraInfo.CAMERA_FACING_FRONT) { + mFrontCameraId = i; + log("Front camera ID is: " + mFrontCameraId); + } + } + } + + /** + * Return the number of cameras supported by the device + * + * @return number of cameras + */ + public int getNumberOfCameras() { + return mNumberOfCameras; + } + + /** + * Open the camera hardware + * + * @param cameraId front or the back camera to open + * @return true if the camera was opened successfully + * @throws Exception + */ + public synchronized boolean open(int cameraId) + throws Exception { + // Check if device policy has disabled the camera. + DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (dpm == null) { + throw new Exception("DevicePolicyManager not available"); + } + + if (dpm.getCameraDisabled(null)) { + throw new Exception("Camera is disabled"); + } + + if (mCameraDevice != null && mCameraId != cameraId) { + mCameraDevice.release(); + mCameraDevice = null; + mCameraId = CAMERA_UNKNOWN; + } + if (mCameraDevice == null) { + try { + if (DBG) log("opening camera " + cameraId); + mCameraDevice = android.hardware.Camera.open(cameraId); + mCameraId = cameraId; + } catch (RuntimeException e) { + loge("fail to connect Camera" + e); + throw new Exception(e); + } + mParameters = mCameraDevice.getParameters(); + } else { + try { + mCameraDevice.reconnect(); + } catch (IOException e) { + loge("reconnect failed."); + throw new Exception(e); + } + setCameraParameters(mParameters); + } + mCameraState = CameraState.PREVIEW_STOPPED; + return true; + } + + /** + * Start the camera preview if camera was opened previously + * + * @param mSurfaceTexture Surface on which to draw the camera preview + * @throws IOException + */ + public void startPreview(SurfaceTexture mSurfaceTexture) throws IOException { + if (mCameraState != CameraState.PREVIEW_STOPPED) { + loge("startPreview: Camera state " + mCameraState + + " is not the right camera state for this operation"); + return; + } + if (mCameraDevice != null) { + if (DBG) log("starting preview"); + + // Set the SurfaceTexture to be used for preview + mCameraDevice.setPreviewTexture(mSurfaceTexture); + + // Set the Preview Call Back to show the camera frames on UI + mCameraDevice.setPreviewCallback(this); + + setDisplayOrientation(); + mCameraDevice.startPreview(); + mCameraState = CameraState.PREVIEW_STARTED; + } + } + + /** + * Close the camera hardware if the camera was opened previously + */ + public synchronized void close() { + if (mCameraState == CameraState.CAMERA_CLOSED) { + loge("close: Camera state " + mCameraState + + " is not the right camera state for this operation"); + return; + } + + if (mCameraDevice != null) { + if (DBG) log("closing camera"); + mCameraDevice.stopPreview(); // Stop preview + mCameraDevice.release(); + } + mCameraDevice = null; + mParameters = null; + mCameraId = CAMERA_UNKNOWN; + mCameraState = CameraState.CAMERA_CLOSED; + } + + /** + * Stop the camera preview if the camera is open and the preview is not + * already started + */ + public void stopPreview() { + if (mCameraState != CameraState.PREVIEW_STARTED) { + loge("stopPreview: Camera state " + mCameraState + + " is not the right camera state for this operation"); + return; + } + if (mCameraDevice != null) { + if (DBG) log("stopping preview"); + mCameraDevice.setPreviewCallback(null); + mCameraDevice.stopPreview(); + } + mCameraState = CameraState.PREVIEW_STOPPED; + } + + /** + * Return the camera parameters that specifies the current settings of the + * camera + * + * @return camera parameters + */ + public Parameters getCameraParameters() { + if (mCameraDevice == null) { + return null; + } + return mParameters; + } + + /** + * Set the camera parameters + * + * @param parameters to be set + */ + public void setCameraParameters(Parameters parameters) { + log("setCameraParameters mCameraDevice=" + mCameraDevice + "parameters =" + parameters); + if (mCameraDevice == null || parameters == null) { + return; + } + mParameters = parameters; + mCameraDevice.setParameters(parameters); + } + + /** + * Get the camera ID for the back camera + * + * @return camera ID + */ + public int getBackCameraId() { + return mBackCameraId; + } + + /** + * Get the camera ID for the front camera + * + * @return camera ID + */ + public int getFrontCameraId() { + return mFrontCameraId; + } + + /** + * Return the current camera state + * + * @return current state of the camera state machine + */ + public CameraState getCameraState() { + return mCameraState; + } + + /** + * Set the display texture for the camera + * + * @param surfaceTexture + */ + public void setDisplay(SurfaceTexture surfaceTexture) { + // Set the SurfaceTexture to be used for preview + if (mCameraDevice == null) return; + try { + mCameraDevice.setPreviewTexture(surfaceTexture); + } catch (IOException e) { + throw new RuntimeException("setPreviewDisplay failed", e); + } + } + + /** + * Set the texture view for the camera + * + * @param textureView + */ + public void setDisplay(TextureView textureView) { + // Set the SurfaceTexture to be used for preview + if (mCameraDevice == null) return; + try { + mCameraDevice.setPreviewTexture(textureView.getSurfaceTexture()); + } catch (IOException e) { + throw new RuntimeException("setPreviewDisplay failed", e); + } + } + + /** + * Gets the supported preview sizes. + * + * @return a list of Size object. This method will always return a list + * with at least one element. + */ + public List getSupportedPreviewSizes() { + if (mCameraDevice == null) return null; + return mCameraDevice.getParameters().getSupportedPreviewSizes(); + } + + /** + * Returns the direction of the currently open camera + * + * @return one of the following possible values + * - CameraInfo.CAMERA_FACING_FRONT + * - CameraInfo.CAMERA_FACING_BACK + * - CAMERA_UNKNOWN - No Camera active + */ + public int getCameraDirection() { + if (mCameraDevice == null) return CAMERA_UNKNOWN; + + return (mCameraId == mFrontCameraId) ? CameraInfo.CAMERA_FACING_FRONT + : CameraInfo.CAMERA_FACING_BACK; + } + + /** + * Set the camera display orientation based on the screen rotation + * and the camera direction + */ + public void setDisplayOrientation() { + android.hardware.Camera.CameraInfo info = + new android.hardware.Camera.CameraInfo(); + int result; + int degrees = 0; + int rotation = 0; + + // Get display rotation + WindowManager wm = (WindowManager) mContext.getSystemService( + Context.WINDOW_SERVICE); + if (wm == null) { + loge("WindowManager not available"); + return; + } + + rotation = wm.getDefaultDisplay().getRotation(); + switch (rotation) { + case Surface.ROTATION_0: degrees = 0; break; + case Surface.ROTATION_90: degrees = 90; break; + case Surface.ROTATION_180: degrees = 180; break; + case Surface.ROTATION_270: degrees = 270; break; + default: + loge("setDisplayOrientation: Unexpected rotation: " + rotation); + } + + android.hardware.Camera.getCameraInfo(mCameraId, info); + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + result = (info.orientation + degrees) % 360; + result = (360 - result) % 360; // compensate the mirror + } else { // back-facing + result = (info.orientation - degrees + 360) % 360; + } + mCameraDevice.setDisplayOrientation(result); + } + + /** + * Called as preview frames are displayed. The frames are passed to IMS DPL + * layer to be sent to the far end device + */ + public void onPreviewFrame(byte[] data, Camera camera) { + if (MediaHandler.canSendPreview()) { + MediaHandler.sendPreviewFrame(data); + } + } + + private void log(String msg) { + Log.d(TAG, msg); + } + + private void loge(String msg) { + Log.e(TAG, msg); + } +} diff --git a/src/com/android/incallui/GlowPadWrapper.java b/src/com/android/incallui/GlowPadWrapper.java index 28ccb956..074f8971 100644 --- a/src/com/android/incallui/GlowPadWrapper.java +++ b/src/com/android/incallui/GlowPadWrapper.java @@ -1,4 +1,8 @@ /* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +27,7 @@ import android.util.AttributeSet; import android.view.View; import com.android.incallui.widget.multiwaveview.GlowPadView; +import com.android.services.telephony.common.CallDetails; /** * @@ -105,13 +110,38 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger } } + private int toCallType(int resId) { + int callType = CallDetails.CALL_TYPE_VOICE; + switch (resId) { + case R.drawable.ic_lockscreen_answer_video: + callType = CallDetails.CALL_TYPE_VT; + break; + case R.drawable.ic_lockscreen_answer_tx_video: + callType = CallDetails.CALL_TYPE_VT_TX; + break; + case R.drawable.ic_lockscreen_answer_rx_video: + callType = CallDetails.CALL_TYPE_VT_RX; + break; + case R.drawable.ic_lockscreen_answer: + callType = CallDetails.CALL_TYPE_VOICE; + break; + default: + Log.wtf(this, "Unknown resource id, resId=" + resId); + break; + } + return callType; + } + @Override public void onTrigger(View v, int target) { Log.d(this, "onTrigger()"); final int resId = getResourceIdForTarget(target); switch (resId) { + case R.drawable.ic_lockscreen_answer_video: + case R.drawable.ic_lockscreen_answer_tx_video: + case R.drawable.ic_lockscreen_answer_rx_video: case R.drawable.ic_lockscreen_answer: - mAnswerListener.onAnswer(); + mAnswerListener.onAnswer(toCallType(resId)); mTargetTriggered = true; break; case R.drawable.ic_lockscreen_decline: @@ -143,7 +173,7 @@ public class GlowPadWrapper extends GlowPadView implements GlowPadView.OnTrigger } public interface AnswerListener { - void onAnswer(); + void onAnswer(int callType); void onDecline(); void onText(); } diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index 60410bd3..92343d3e 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -1,4 +1,8 @@ /* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +20,12 @@ package com.android.incallui; +import java.lang.reflect.Array; +import java.util.ArrayList; + import com.android.services.telephony.common.Call; import com.android.services.telephony.common.Call.State; +import com.android.services.telephony.common.CallDetails; import android.app.Activity; import android.app.AlertDialog; @@ -26,6 +34,7 @@ import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.res.Configuration; +import android.content.res.Resources; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; @@ -50,6 +59,7 @@ public class InCallActivity extends Activity { private ConferenceManagerFragment mConferenceManagerFragment; private boolean mIsForegroundActivity; private AlertDialog mDialog; + private AlertDialog mModifyCallPromptDialog; /** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */ private boolean mShowDialpadRequested; @@ -403,6 +413,133 @@ public class InCallActivity extends Activity { } } + // The function is called when Modify Call button gets pressed. + // The function creates and displays modify call options. + public void displayModifyCallOptions(final int callId) { + final ArrayList items = new ArrayList(); + final ArrayList itemToCallType = new ArrayList(); + final Resources res = getResources(); + + // Prepare the string array and mapping. + items.add(res.getText(R.string.modify_call_option_voice)); + itemToCallType.add(CallDetails.CALL_TYPE_VOICE); + + items.add(res.getText(R.string.modify_call_option_vt_rx)); + itemToCallType.add(CallDetails.CALL_TYPE_VT_RX); + + items.add(res.getText(R.string.modify_call_option_vt_tx)); + itemToCallType.add(CallDetails.CALL_TYPE_VT_TX); + + items.add(res.getText(R.string.modify_call_option_vt)); + itemToCallType.add(CallDetails.CALL_TYPE_VT); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.modify_call_option_title); + final AlertDialog alert; + + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Toast.makeText(getApplicationContext(), items.get(item), Toast.LENGTH_SHORT).show(); + final int selCallType = itemToCallType.get(item); + Log.d("Videocall", + "ModifyCall called: upgrade to " + CallUtils.fromCallType(selCallType)); + InCallPresenter.getInstance().sendModifyCallRequest(callId, selCallType); + dialog.dismiss(); + } + }; + int currCallType = CallUtils.getCallType( CallList.getInstance().getCall(callId)); + int index = itemToCallType.indexOf(currCallType); + builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), index, listener); + alert = builder.create(); + alert.show(); + } + + public void displayModifyCallConsentDialog(Call call) { + log("VideoCall: displayModifyCallConsentDialog"); + + if (mModifyCallPromptDialog != null) { + log("VideoCall: - DISMISSING mModifyCallPromptDialog."); + mModifyCallPromptDialog.dismiss(); // safe even if already dismissed + mModifyCallPromptDialog = null; + } + + boolean error = CallUtils.hasCallModifyFailed(call); + int callType = CallUtils.getProposedCallType(call); + if (!error) { + String str = getResources().getString(R.string.accept_modify_call_request_prompt); + if (callType == CallDetails.CALL_TYPE_VT) { + str = getResources().getString(R.string.upgrade_vt_prompt); + } else if (callType == CallDetails.CALL_TYPE_VT_TX) { + str = getResources().getString(R.string.upgrade_vt_tx_prompt); + } else if (callType == CallDetails.CALL_TYPE_VT_RX) { + str = getResources().getString(R.string.upgrade_vt_rx_prompt); + } + + final ModifyCallConsentListener onConsentListener = + new ModifyCallConsentListener(call); + mModifyCallPromptDialog = new AlertDialog.Builder(this) + .setMessage(str) + .setPositiveButton(R.string.modify_call_prompt_yes, + onConsentListener) + .setNegativeButton(R.string.modify_call_prompt_no, + onConsentListener) + .setOnDismissListener(onConsentListener) + .create(); + mModifyCallPromptDialog.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + + mModifyCallPromptDialog.show(); + + } else { + log("VideoCall: Modify Call request failed."); + String errorMsg = getResources().getString(R.string.modify_call_failure_str); + toast(errorMsg); + // We are not explicitly dismissing mModifyCallPromptDialog + // here since it is dismissed at the beginning of this function. + // Note, connection type change will be rejected by + // the Modify Call Consent dialog. + } + } + + private class ModifyCallConsentListener implements DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { + private boolean mClicked = false; + private Call mCall; + + public ModifyCallConsentListener(Call call) { + mCall = call; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + log("VideoCall: ConsentDialog: Clicked on button with ID: " + which); + mClicked = true; + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + InCallPresenter.getInstance().modifyCallConfirm(true, mCall); + break; + case DialogInterface.BUTTON_NEGATIVE: + InCallPresenter.getInstance().modifyCallConfirm(false, mCall); + break; + default: + loge("videocall: No handler for this button, ID:" + which); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + if (!mClicked) { + log("VideoCall: ConsentDialog: Dismissing the dialog"); + InCallPresenter.getInstance().modifyCallConfirm(false, mCall); + } + } + } + + public void onAvpUpgradeFailure(String errorString) { + Log.e(this,"VideoCall: onAvpUpgradeFailure: errorString: " + errorString); + toast(getResources().getString(R.string.modify_call_failure_str)); + } + public void showPostCharWaitDialog(int callId, String chars) { final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars); fragment.show(getFragmentManager(), "postCharWait"); @@ -433,7 +570,12 @@ public class InCallActivity extends Activity { mDialog.dismiss(); mDialog = null; } + mAnswerFragment.dismissPendingDialogues(); + if (mModifyCallPromptDialog != null) { + mModifyCallPromptDialog.dismiss(); + mModifyCallPromptDialog = null; + } } /** @@ -510,4 +652,13 @@ public class InCallActivity extends Activity { mDialog = null; InCallPresenter.getInstance().onDismissDialog(); } + + private void log(String msg) { + Log.d(this, msg); + } + + private void loge(String msg) { + Log.e(this, msg); + } + } diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 1acbf196..8ba186fa 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -1,4 +1,8 @@ /* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,6 +62,30 @@ public class InCallPresenter implements CallList.Listener { private boolean mServiceConnected = false; private static String LOG_TAG = "InCallPresenter"; + /** + * This table is for deciding whether consent is + * required while upgrade/downgrade from one calltype + * to other + * Read calltype transition from row to column + * 1 => Consent of user is required + * 0 => No consent required + * eg. from VOLTE to VT-TX, consent is needed so + * row 0, col 1 is set to 1 + * + * User consent is needed for all upgrades and not + * needed for downgrades + * + * VOLTE VT-TX VT-RX VT + * VOLTE | 0 | 1 | 1 | 1 + * VT-TX | 0 | 0 | 1 | 1 + * VT-RX | 0 | 1 | 0 | 1 + * VT | 0 | 0 | 0 | 0 + */ + private int[][] mVideoConsentTable = {{0, 1, 1, 1}, + {0, 0, 1, 1}, + {0, 1, 0, 1}, + {0, 0, 0, 0}}; + /** * Is true when the activity has been previously started. Some code needs to know not just if * the activity is currently up, but if it had been previously shown in foreground for this @@ -66,6 +94,8 @@ public class InCallPresenter implements CallList.Listener { */ private boolean mIsActivityPreviouslyStarted = false; + private boolean isImsMediaInitialized = false; + public static synchronized InCallPresenter getInstance() { if (sInCallPresenter == null) { sInCallPresenter = new InCallPresenter(); @@ -105,6 +135,9 @@ public class InCallPresenter implements CallList.Listener { // will kick off an update and the whole process can start. mCallList.addListener(this); + // Initialize VideoCallManager. Instantiates the singleton. + VideoCallManager.getInstance(mContext); + Log.d(this, "Finished InCallPresenter.setUp"); } @@ -131,6 +164,65 @@ public class InCallPresenter implements CallList.Listener { } } + /** + * Sends modify call request to the other party. + * @param callId id of the call to modify. + * @param callType Proposed call type. + */ + public void sendModifyCallRequest(int callId, int callType) { + log("VideoCall: Sending modify call request. callId=" + callId + " callType=" + callType); + // CallCommandClient.getInstance().modifyCall(callId, callType); + } + + /** + * Accepts/Rejects modify call request. + * + * @param accept true if the proposed call type is accepted, false otherwise. + * @param call Call which call type change to be confirmed/rejected. + */ + public void modifyCallConfirm(boolean accept, Call call) { + log("VideoCall: ModifyCallConfirm: accept=" + accept + " call=" + call); + final int callId = call.getCallId(); + int callType = accept ? CallUtils.getProposedCallType(call) : CallUtils.getCallType(call); + // CallCommandClient.getInstance().modifyCallConfirm(call.getCallId(), callType); + } + + /** + * Handles modify call request and shows dialog to user for accepting or + * rejecting the modify call + */ + public void onModifyCallRequest(int callId) { + final Call call = CallList.getInstance().getCall(callId); + if (call != null) { + + final int currCallType = CallUtils.getCallType(call); + final int proposedCallType = CallUtils.getProposedCallType(call); + final boolean error = CallUtils.hasCallModifyFailed(call); + + log("VideoCall onMoifyCallRequest: CallId = " + callId + " currCallType= " + + currCallType + + " proposedCallType= " + proposedCallType + " error=" + error); + if (isUserConsentRequired(proposedCallType, currCallType)) { + if (mInCallActivity != null) { + mInCallActivity.displayModifyCallConsentDialog(call); + } else { + Log.e(this, "VideoCall: onMoifyCallRequest: InCallActivity is null."); + } + } + } else { + loge("onModifyCallRequest: Can't find call with callId="+callId); + } + } + + public void onAvpUpgradeFailure(String errorString) { + if (mInCallActivity != null) { + mInCallActivity.onAvpUpgradeFailure(errorString); + } else { + Log.e(this, "VideoCall: onAvpUpgradeFailure: InCallActivity is null."); + Log.e(this, "VideoCall: onAvpUpgradeFailure: error=" + errorString); + } + } + /** * Called when the UI begins or ends. Starts the callstate callbacks if the UI just began. * Attempts to tear down everything if the UI just ended. See #tearDown for more insight on @@ -226,6 +318,8 @@ public class InCallPresenter implements CallList.Listener { CallCommandClient.getInstance().setSystemBarNavigationEnabled(true); } + onPhoneStateChange(newState, mInCallState); + // Set the new state before announcing it to the world Log.i(this, "Phone switching state: " + mInCallState + " -> " + newState); mInCallState = newState; @@ -246,6 +340,8 @@ public class InCallPresenter implements CallList.Listener { public void onIncomingCall(Call call) { InCallState newState = startOrFinishUi(InCallState.INCOMING); + onPhoneStateChange(newState, mInCallState); + Log.i(this, "Phone switching state: " + mInCallState + " -> " + newState); mInCallState = newState; @@ -754,4 +850,36 @@ public class InCallPresenter implements CallList.Listener { public interface IncomingCallListener { public void onIncomingCall(InCallState state, Call call); } + + private void onPhoneStateChange(InCallState newState, InCallState oldState) { + if ( newState != oldState) { + initMediaHandler(newState); + } + } + + private void initMediaHandler(InCallState newState) { + boolean hasImsCall = CallUtils.hasImsCall(CallList.getInstance()); + Log.i(this, "initMediaHandler: hasImsCall: " + hasImsCall + " isImsMediaInitialized: " + + isImsMediaInitialized); + + if (hasImsCall && !isImsMediaInitialized) { + isImsMediaInitialized = true; + VideoCallManager.getInstance(mContext).onMediaRequest(isImsMediaInitialized); + } else if (isImsMediaInitialized && !hasImsCall) { + isImsMediaInitialized = false; + VideoCallManager.getInstance(mContext).onMediaRequest(isImsMediaInitialized); + } + } + + private boolean isUserConsentRequired(int callType, int prevCallType) { + return mVideoConsentTable[prevCallType][callType] == 1; + } + + private void log(String msg) { + Log.d(this, msg); + } + + private void loge(String msg) { + Log.e(this, msg); + } } diff --git a/src/com/android/incallui/MediaHandler.java b/src/com/android/incallui/MediaHandler.java new file mode 100644 index 00000000..78642972 --- /dev/null +++ b/src/com/android/incallui/MediaHandler.java @@ -0,0 +1,228 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.graphics.SurfaceTexture; +import android.os.Handler; +import android.util.Log; + +/** + * Provides an interface to handle the media part of the video telephony call + */ +public class MediaHandler extends Handler { + + public static final int DPL_INIT_SUCCESSFUL = 0; + public static final int DPL_INIT_FAILURE = -1; + public static final int DPL_INIT_MULTIPLE = -2; + + private static final String TAG = "VideoCall_MediaHandler"; + + private static SurfaceTexture mSurface; + + private static boolean mInitCalledFlag = false; + + private static native int nativeInit(); + private static native void nativeDeInit(); + private static native void nativeHandleRawFrame(byte[] frame); + private static native int nativeSetSurface(SurfaceTexture st); + private static native short nativeGetNegotiatedFPS(); + private static native int nativeGetNegotiatedHeight(); + private static native int nativeGetNegotiatedWidth(); + private static native void nativeRegisterForMediaEvents(MediaHandler instance); + + public static final int PARAM_READY_EVT = 1; + public static final int START_READY_EVT = 2; + + /* + * Initializing default negotiated parameters to a working set of valuesso + * that the application does not crash in case we do not get the Param ready + * event + */ + private static int mNegotiatedHeight = 240; + private static int mNegotiatedWidth = 320; + private static short mNegotiatedFps = 20; + + private MediaEventListener mMediaEventListener; + + private static boolean mIsReadyToReceivePreview = false; + + public interface MediaEventListener { + void onParamReadyEvent(); + } + + static { + System.loadLibrary("vt_jni"); + } + + /* + * Initialize Media + * @return + DPL_INIT_SUCCESSFUL 0 initialization is successful. + DPL_INIT_FAILURE -1 error in initialization of QMI or other components. + DPL_INIT_MULTIPLE -2 trying to initialize an already initialized library. + */ + public int init() { + if (!mInitCalledFlag) { + //Initialize mIsReadyToReceivePreview to false to begin with + mIsReadyToReceivePreview = false; + int error = nativeInit(); + Log.d(TAG, "init called error = " + error); + switch (error) { + case DPL_INIT_SUCCESSFUL: + mInitCalledFlag = true; + registerForMediaEvents(this); + break; + case DPL_INIT_FAILURE: + mInitCalledFlag = false; + break; + case DPL_INIT_MULTIPLE: + mInitCalledFlag = true; + Log.e(TAG, "Dpl init is called multiple times"); + error = DPL_INIT_SUCCESSFUL; + break; + } + return error; + } + + // Dpl is already initialized. So return success + return DPL_INIT_SUCCESSFUL; + } + + /* + * Deinitialize Media + */ + public static void deInit() { + Log.d(TAG, "deInit called"); + nativeDeInit(); + mInitCalledFlag = false; + } + + /** + * Send the camera preview frames to the media module to be sent to the far + * end party + * @param frame raw frames from the camera + */ + public static void sendPreviewFrame(byte[] frame) { + nativeHandleRawFrame(frame); + } + + /** + * Send the SurfaceTexture to media module + * @param st + */ + public static void setSurface(SurfaceTexture st) { + Log.d(TAG, "setSurface(SurfaceTexture: " + st + ")"); + mSurface = st; + nativeSetSurface(st); + } + + /** + * Send the SurfaceTexture to media module. This should be called only for + * re-sending an already created surface + */ + public static void setSurface() { + Log.d(TAG, "setSurface()"); + if (mSurface == null) { + Log.e(TAG, "sSurface is null. So not passing it down"); + return; + } + nativeSetSurface(mSurface); + } + + /** + * Get Negotiated FPS + */ + public static short getNegotiatedFPS() { + Log.d(TAG, "Negotiated FPS = " + mNegotiatedFps); + return mNegotiatedFps; + } + + /** + * Get Negotiated Height + */ + public static int getNegotiatedHeight() { + Log.d(TAG, "Negotiated Height = " + mNegotiatedHeight); + return mNegotiatedHeight; + } + + /** + * Get Negotiated Width + */ + public static int getNegotiatedWidth() { + Log.d(TAG, "Negotiated Width = " + mNegotiatedWidth); + return mNegotiatedWidth; + } + + public static synchronized boolean canSendPreview() { + return MediaHandler.mIsReadyToReceivePreview; + } + + public static synchronized void setIsReadyToReceivePreview(boolean flag) { + Log.d(TAG, "setIsReadyToReceivePreview = " + flag); + MediaHandler.mIsReadyToReceivePreview = flag; + } + + /** + * Register for event that will invoke + * {@link MediaHandler#onMediaEvent(int)} + */ + private static void registerForMediaEvents(MediaHandler instance) { + Log.d(TAG, "Registering for Media Callback Events"); + nativeRegisterForMediaEvents(instance); + } + + public void setMediaEventListener(MediaEventListener listener) { + mMediaEventListener = listener; + } + + /** + * Callback method that is invoked when Media events occur + */ + public void onMediaEvent(int eventId) { + Log.d(TAG, "onMediaEvent eventId = " + eventId); + switch (eventId) { + case PARAM_READY_EVT: + Log.d(TAG, "Received PARAM_READY_EVT. Updating negotiated values"); + mNegotiatedHeight = nativeGetNegotiatedHeight(); + mNegotiatedWidth = nativeGetNegotiatedWidth(); + mNegotiatedFps = nativeGetNegotiatedFPS(); + if (mMediaEventListener != null) { + mMediaEventListener.onParamReadyEvent(); + } + break; + case START_READY_EVT: + Log.d(TAG, "Received START_READY_EVT. Camera frames can be sent now"); + setIsReadyToReceivePreview(true); + break; + default: + Log.e(TAG, "Received unknown event id=" + eventId); + } + + } +} diff --git a/src/com/android/incallui/VideoCallManager.java b/src/com/android/incallui/VideoCallManager.java new file mode 100644 index 00000000..ea4ffd4b --- /dev/null +++ b/src/com/android/incallui/VideoCallManager.java @@ -0,0 +1,309 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import java.io.IOException; + +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.util.Log; +import com.android.incallui.CameraHandler.CameraState; + +import java.util.List; + +/** + * Provides an interface for the applications to interact with Camera for the + * near end preview and sending the frames to the far end and also with Media + * engine to render the far end video during a Video Call Session. + */ +public class VideoCallManager { + private static final String TAG = "VideoCallManager"; + private static VideoCallManager mInstance; // Use a singleton + private CameraHandler mCameraHandler; + private MediaHandler mMediaHandler; + + /** @hide */ + private VideoCallManager(Context context) { + log("Instantiating VideoCallManager"); + mCameraHandler = CameraHandler.getInstance(context); + mMediaHandler = new MediaHandler(); + } + + /** + * This method returns the single instance of VideoCallManager object + * + * @param mContext + */ + public static synchronized VideoCallManager getInstance(Context context) { + if (mInstance == null) { + mInstance = new VideoCallManager(context); + } + return mInstance; + } + + /** + * Called to notify if media initialization/deinitialization is necessary. + * + * @param init true if the media should be initialized, false if it should be deinitialized. + */ + public void onMediaRequest(boolean init) { + if (!init) { + MediaHandler.deInit(); + } else if (mMediaHandler.init() == MediaHandler.DPL_INIT_SUCCESSFUL) { + MediaHandler.setSurface(); // TODO: Pass the surface if the surface is created. + } + } + + /** + * Initialize the Media + * @deprecated + */ + public int mediaInit() { + return mMediaHandler.init(); + } + + /** + * Deinitialize the Media + * @deprecated + */ + public void mediaDeInit() { + MediaHandler.deInit(); + } + + /** + * Send the SurfaceTexture to Media module + * @param st SurfaceTexture to be passed + */ + public void setFarEndSurface(SurfaceTexture st) { + MediaHandler.setSurface(st); + } + + /** + * Send the SurfaceTexture to Media module + */ + public void setFarEndSurface() { + MediaHandler.setSurface(); + } + + /** + * Get negotiated height + */ + public int getNegotiatedHeight() { + return MediaHandler.getNegotiatedHeight(); + } + + /** + * Get negotiated width + */ + public int getNegotiatedWidth() { + return MediaHandler.getNegotiatedWidth(); + } + + /** + * Get negotiated FPS + */ + public int getNegotiatedFPS() { + return MediaHandler.getNegotiatedFPS(); + } + + public static boolean isMediaReadyToReceivePreview() { + return MediaHandler.canSendPreview(); + } + + public static void setIsMediaReadyToReceivePreview(boolean flag) { + MediaHandler.setIsReadyToReceivePreview(flag); + } + + /** + * Return the number of cameras supported by the device + * + * @return number of cameras + */ + public int getNumberOfCameras() { + return mCameraHandler.getNumberOfCameras(); + } + + /** + * Open the camera hardware + * + * @param cameraId front or the back camera to open + * @return true if the camera was opened successfully + * @throws Exception + */ + public synchronized boolean openCamera(int cameraId) throws Exception { + return mCameraHandler.open(cameraId); + } + + /** + * Start the camera preview if camera was opened previously + * + * @param mSurfaceTexture Surface on which to draw the camera preview + * @throws IOException + */ + public void startCameraPreview(SurfaceTexture surfaceTexture) throws IOException { + mCameraHandler.startPreview(surfaceTexture); + } + + /** + * Close the camera hardware if the camera was opened previously + */ + public void closeCamera() { + mCameraHandler.close(); + } + + /** + * Stop the camera preview if the camera is open and the preview is not + * already started + */ + public void stopCameraPreview() { + mCameraHandler.stopPreview(); + } + + /** + * Return the camera parameters that specifies the current settings of the + * camera + * + * @return camera parameters + */ + public Parameters getCameraParameters() { + return mCameraHandler.getCameraParameters(); + } + + /** + * Set the camera parameters + * + * @param parameters to be set + */ + public void setCameraParameters(Parameters parameters) { + mCameraHandler.setCameraParameters(parameters); + } + + /** + * Get the camera ID for the back camera + * + * @return camera ID + */ + public int getBackCameraId() { + return mCameraHandler.getBackCameraId(); + } + + /** + * Get the camera ID for the front camera + * + * @return camera ID + */ + public int getFrontCameraId() { + return mCameraHandler.getFrontCameraId(); + } + + /** + * Return the current camera state + * + * @return current state of the camera state machine + */ + public CameraState getCameraState() { + return mCameraHandler.getCameraState(); + } + + /** + * Set the display texture for the camera + * + * @param surfaceTexture + */ + public void setDisplay(SurfaceTexture surfaceTexture) { + mCameraHandler.setDisplay(surfaceTexture); + } + + /** + * Gets the camera preview size that matches the given width or height to + * preserve the aspect ratio. + * + * @param size specifies height or width of the camera surface + * @param isHeight specifies if the given size is height if true or width + * if false + * @return width and height of camera preview as a Point + * point.x = width + * point.y = height + */ + public Size getCameraPreviewSize(int targetSize, boolean isHeight) { + double minDiff = Double.MAX_VALUE; + Size optimalSize = null; + + List mSupportedPreviewSizes = mCameraHandler.getSupportedPreviewSizes(); + if (mSupportedPreviewSizes == null) return null; // Camera not yet open + + // Try to find a size that matches closely with the required height or + // width + for (Size size : mSupportedPreviewSizes) { + int srcSize = 0; + if (isHeight) { + srcSize = size.height; + } + else { + srcSize = size.width; + } + + if (Math.abs(srcSize - targetSize) < minDiff) { + optimalSize = size; + minDiff = Math.abs(srcSize - targetSize); + } + } + return optimalSize; + } + + /** + * Returns the direction of the currently open camera + * + * @return one of the following possible values + * - CameraInfo.CAMERA_FACING_FRONT + * - CameraInfo.CAMERA_FACING_BACK + * - -1 - No Camera active + */ + public int getCameraDirection() { + return mCameraHandler.getCameraDirection(); + } + + /** + * Set the camera display orientation based on the screen rotation and the + * camera direction + */ + void setCameraDisplayOrientation() { + mCameraHandler.setDisplayOrientation(); + } + + public void setOnParamReadyListener(VideoCallPanel.ParamReadyListener listener) { + mMediaHandler.setMediaEventListener(listener); + } + + private void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/src/com/android/incallui/VideoCallPanel.java b/src/com/android/incallui/VideoCallPanel.java new file mode 100644 index 00000000..2448c49e --- /dev/null +++ b/src/com/android/incallui/VideoCallPanel.java @@ -0,0 +1,641 @@ +/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.os.SystemProperties; +import android.util.AttributeSet; +import android.util.Log; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import com.android.incallui.CameraHandler.CameraState; +import com.android.services.telephony.common.CallDetails; + +import java.io.IOException; +import java.util.List; + +/** + * Helper class to initialize and run the InCallScreen's "Video Call" UI. + */ +public class VideoCallPanel extends RelativeLayout implements TextureView.SurfaceTextureListener, View.OnClickListener { + private static final int CAMERA_UNKNOWN = -1; + private static final String LOG_TAG = "VideoCallPanel"; + private static final boolean DBG = true; + + private static final int MEDIA_TO_CAMERA_CONV_UNIT = 1000; + + private Context mContext; + private VideoCallManager mVideoCallManager; + + // "Video Call" UI elements and state + private ViewGroup mVideoCallPanel; + private ZoomControlBar mZoomControl; + private TextureView mFarEndView; + private TextureView mCameraPreview; + private SurfaceTexture mCameraSurface; + private SurfaceTexture mFarEndSurface; + private ImageView mCameraPicker; + + // Camera related + private Parameters mParameters; + private int mZoomMax; + private int mZoomValue; // The current zoom value + Size mPreviewSize; + + // Multiple cameras support + private int mNumberOfCameras; + private int mFrontCameraId; + private int mBackCameraId; + private int mCameraId; + + // Property used to indicate that the Media running in loopback mode + private boolean mIsMediaLoopback = false; + + // Flag to indicate if camera is needed for a certain call type. + // For eg. VT_RX call will not need camera + private boolean mCameraNeeded = false; + + /** + * This class implements the zoom listener for zoomControl + */ + private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener { + @Override + public void onZoomValueChanged(int index) { + VideoCallPanel.this.onZoomValueChanged(index); + } + } + + /** + * This class implements the listener for PARAM READY EVENT + */ + public class ParamReadyListener implements MediaHandler.MediaEventListener { + @Override + public void onParamReadyEvent() { + CameraState cameraState = mVideoCallManager.getCameraState(); + if (DBG) log("onParamReadyEvent cameraState= " + cameraState); + if (cameraState == CameraState.PREVIEW_STARTED) { + // If camera is already capturing stop preview, reset the + // parameters and then start preview again + try { + mVideoCallManager.stopCameraPreview(); + initializeCameraParams(); + mVideoCallManager.startCameraPreview(mCameraSurface); + } catch (IOException ioe) { + loge("Exception onParamReadyEvent stopping and starting preview " + + ioe.toString()); + } + } + + } + } + + public VideoCallPanel(Context context) { + super(context); + mContext = context; + } + + public VideoCallPanel(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + } + + public VideoCallPanel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mContext = context; + } + + /** + * Finalize view from inflation. + */ + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + if (DBG) log("onFinishInflate(this = " + this + ")..."); + + // Check the Media loopback property + int property = SystemProperties.getInt("net.lte.VT_LOOPBACK_ENABLE", 0); + mIsMediaLoopback = (property == 1) ? true : false; + if (DBG) log("Is Media running in loopback mode: " + mIsMediaLoopback); + + // Get UI widgets + mVideoCallPanel = (ViewGroup) findViewById(R.id.videoCallPanel); + mZoomControl = (ZoomControlBar) findViewById(R.id.zoom_control); + mFarEndView = (TextureView) findViewById(R.id.video_view); + mCameraPreview = (TextureView) findViewById(R.id.camera_view); + mCameraPicker = (ImageView) findViewById(R.id.camera_picker); + + // Set listeners + mCameraPreview.setSurfaceTextureListener(this); + mFarEndView.setSurfaceTextureListener(this); + mCameraPicker.setOnClickListener(this); + + // Get the camera IDs for front and back cameras + mVideoCallManager = VideoCallManager.getInstance(mContext); + mBackCameraId = mVideoCallManager.getBackCameraId(); + mFrontCameraId = mVideoCallManager.getFrontCameraId(); + chooseCamera(true); + + // Check if camera supports dual cameras + mNumberOfCameras = mVideoCallManager.getNumberOfCameras(); + if (mNumberOfCameras > 1) { + mCameraPicker.setVisibility(View.VISIBLE); + } else { + mCameraPicker.setVisibility(View.GONE); + } + + // Set media event listener + mVideoCallManager.setOnParamReadyListener(new ParamReadyListener()); + } + + public void setCameraNeeded(boolean mCameraNeeded) { + this.mCameraNeeded = mCameraNeeded; + } + + /** + * Call is either is either being originated or an MT call is received. + */ + public void onCallInitiating(int callType) { + if (DBG) log("onCallInitiating"); + + // Only for VT TX it is required to default to back camera + boolean chooseFrontCamera = true; + if (callType == CallDetails.CALL_TYPE_VT_TX) { + chooseFrontCamera = false; + } + + chooseCamera(chooseFrontCamera); + + if (callType == CallDetails.CALL_TYPE_VT + || callType == CallDetails.CALL_TYPE_VT_TX) { + mCameraNeeded = true; + } else { + mCameraNeeded = false; + } + } + + /** + * Called during layout when the size of the view has changed. This method + * store the VideoCallPanel size to be later used to resize the camera + * preview accordingly + */ + @Override + protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) { + if (DBG) log("onSizeChanged"); + if (DBG) log("Video Panel width:" + xNew + ", height:" + yNew); + + // Resize preview window if the size of the view changed + resizeCameraPreview(yNew); + resizeFarEndView(xNew, yNew); + } + + /** + * Called when the InCallScreen activity is being paused. This method hides + * the VideoCallPanel so that other activities can use the camera at this + * time. + */ + public void onPause() { + if (DBG) log("onPause"); + mVideoCallPanel.setVisibility(View.GONE); + } + + /** + * This method opens the camera and starts the camera preview + */ + private void initializeCamera() { + if (DBG) log("Initializing camera id=" + mCameraId); + + if (mCameraId == CAMERA_UNKNOWN) { + loge("initializeCamera: Not initializing camera as mCameraId is unknown"); + return; + } + + // Open camera if not already open + if (false == openCamera(mCameraId)) { + return; + } + initializeZoom(); + initializeCameraParams(); + // Start camera preview + startPreview(); + } + + public boolean isCameraInitNeeded() { + return mCameraNeeded + && mVideoCallManager.getCameraState() == CameraState.CAMERA_CLOSED; + } + + /** + * This method crates the camera object if camera is not disabled + * + * @param cameraId ID of the front or the back camera + * @return Camera instance on success, null otherwise + */ + private boolean openCamera(int cameraId) { + boolean result = false; + + try { + return mVideoCallManager.openCamera(cameraId); + } catch (Exception e) { + loge("Failed to open camera device, error " + e.toString()); + return result; + } + } + + /** + * This method starts the camera preview + */ + private void startPreview() { + try { + mCameraPreview.setVisibility(View.VISIBLE); + mVideoCallManager.startCameraPreview(mCameraSurface); + } catch (IOException ioe) { + closeCamera(); + loge("Exception while setting preview texture, " + ioe.toString()); + } + } + + /** + * This method disconnect and releases the camera + */ + private void closeCamera() { + mVideoCallManager.closeCamera(); + } + + /** + * This method stops the camera preview + */ + private void stopPreview() { + mCameraPreview.setVisibility(View.GONE); + mVideoCallManager.stopCameraPreview(); + } + + /* Implementation of listeners */ + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + if (surface.equals(mCameraPreview.getSurfaceTexture())) { + if (DBG) log("Camera surface texture created"); + mCameraSurface = surface; + if (isCameraInitNeeded()) { + initializeCamera(); + } else { + // Set preview display if the surface is being created and preview + // was already started. That means preview display was set to null + // and we need to set it now. + mVideoCallManager.setDisplay(mCameraSurface); + } + } else if (surface.equals(mFarEndView.getSurfaceTexture())) { + if (DBG) log("Video surface texture created"); + mFarEndSurface = surface; + mVideoCallManager.setFarEndSurface(mFarEndSurface); + } + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + if (surface.equals(mCameraPreview.getSurfaceTexture())) { + if (DBG) log("CameraPreview surface texture destroyed"); + stopPreview(); + closeCamera(); + mCameraSurface = null; + } else if (surface.equals(mFarEndView.getSurfaceTexture())) { + if (DBG) log("FarEndView surface texture destroyed"); + mFarEndSurface = null; + mVideoCallManager.setFarEndSurface(null); + } + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + // Invoked every time there's a new Camera preview frame + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + // Ignored camera does all the work for us + } + + /** + * This method is called when the visibility of the VideoCallPanel is changed + */ + @Override + protected void onVisibilityChanged (View changedView, int visibility) { + if (changedView != this || mVideoCallManager == null) { + return; + } + + switch(visibility) + { + case View.INVISIBLE: + case View.GONE: + if (DBG) log("VideoCallPanel View is GONE or INVISIBLE"); + // Stop the preview and close the camera now because other + // activities may need to use it + if (mVideoCallManager.getCameraState() != CameraState.CAMERA_CLOSED) { + stopPreview(); + closeCamera(); + } + break; + case View.VISIBLE: + if (DBG) log("VideoCallPanel View is VISIBLE"); + if (isCameraInitNeeded()) { + initializeCamera(); + } + break; + } + } + + @Override + public void onClick(View v) { + int direction = mVideoCallManager.getCameraDirection(); + + // Switch the camera front/back/off + // The state machine is as follows + // front --> back --> stop preview --> front... + switch(direction) { + case CAMERA_UNKNOWN: + switchCamera(mFrontCameraId); + break; + case Camera.CameraInfo.CAMERA_FACING_FRONT: + switchCamera(mBackCameraId); + break; + case Camera.CameraInfo.CAMERA_FACING_BACK: + switchCamera(CAMERA_UNKNOWN); + break; + } + } + + /** + * This method get the zoom related parameters from the camera and + * initialized the zoom control + */ + private void initializeZoom() { + // Get the parameter to make sure we have the up-to-date zoom value. + mParameters = mVideoCallManager.getCameraParameters(); + if(mParameters == null) { + return; + } + if (!mParameters.isZoomSupported()) { + mZoomControl.setVisibility(View.GONE); // Disable ZoomControl + return; + } + + mZoomControl.setVisibility(View.VISIBLE); // Enable ZoomControl + mZoomMax = mParameters.getMaxZoom(); + // Currently we use immediate zoom for fast zooming to get better UX and + // there is no plan to take advantage of the smooth zoom. + mZoomControl.setZoomMax(mZoomMax); + mZoomControl.setZoomIndex(mParameters.getZoom()); + mZoomControl.setOnZoomChangeListener(new ZoomChangeListener()); + } + + /** + * This method gets called when the zoom control reports that the zoom value + * has changed. This method sets the camera zoom value accordingly. + * @param index + */ + private void onZoomValueChanged(int index) { + mZoomValue = index; + + // Set zoom + if (mParameters.isZoomSupported()) { + mParameters.setZoom(mZoomValue); + mVideoCallManager.setCameraParameters(mParameters); + } + } + + /** + * Initialize camera parameters based on negotiated height, width + */ + private void initializeCameraParams() { + try { + // Get the parameter to make sure we have the up-to-date value. + mParameters = mVideoCallManager.getCameraParameters(); + // Set the camera preview size + if (mIsMediaLoopback) { + // In loopback mode the IMS is hard coded to render the + // camera frames of only the size 176x144 on the far end surface + mParameters.setPreviewSize(176, 144); + } else { + log("Supported Preview Sizes = " + mParameters.getSupportedPreviewSizes()); + log("Set Preview Size directly with negotiated Height = " + + mVideoCallManager.getNegotiatedHeight() + + " negotiated width= " + mVideoCallManager.getNegotiatedWidth()); + mParameters.setPreviewSize(mVideoCallManager.getNegotiatedWidth(), + mVideoCallManager.getNegotiatedHeight()); + setFpsRange(); + } + + mVideoCallManager.setCameraParameters(mParameters); + } catch (RuntimeException e) { + log("Error setting Camera preview size/fps exception=" + e); + log("Supported Preview sizes = " + mParameters.getSupportedPreviewSizes()); + } + } + + public void setPanelElementsVisibility(int callType) { + log("setPanelElementsVisibility: callType= " + callType); + switch (callType) { + case CallDetails.CALL_TYPE_VT: + mCameraPreview.setVisibility(VISIBLE); + mFarEndView.setVisibility(VISIBLE); + if (isCameraInitNeeded()) { + initializeCamera(); + } + log("setPanelElementsVisibility: VT: mCameraPreview:VISIBLE, mFarEndView:VISIBLE"); + break; + case CallDetails.CALL_TYPE_VT_TX: + mCameraPreview.setVisibility(View.VISIBLE); + if (isCameraInitNeeded()) { + initializeCamera(); + } + // Not setting mFarEndView to GONE as receiver side did not get the frames + log("setPanelElementsVisibility VT_TX: mCameraPreview:VISIBLE"); + break; + case CallDetails.CALL_TYPE_VT_RX: + mFarEndView.setVisibility(View.VISIBLE); + // Stop the preview and close the camera now because other + // activities may need to use it + if (mVideoCallManager.getCameraState() != CameraState.CAMERA_CLOSED) { + stopPreview(); + closeCamera(); + } + mCameraPreview.setVisibility(View.GONE); + log("setPanelElementsVisibility VT_RX: mCameraPreview:GONE mFarEndView:VISIBLE"); + break; + default: + log("setPanelElementsVisibility: Default: " + + "VideoCallPanel is " + mVideoCallPanel.getVisibility() + + "mCameraPreview is " + mCameraPreview.getVisibility() + + "mFarEndView is " + mFarEndView.getVisibility()); + break; + } + } + + /** + * Select the best possible fps range from the supported list of fps ranges. + * Find the auto mode range that camera supports and use low value of that + * range and high value as negotiated value For eg. if supported values are + * (7.5,30), (20,20), (10,10), (30,30) and negotiated fps is 25 then fps + * should be set to (7.5,25). + */ + void setFpsRange() { + // Camera apis need the FPS values scaled by 1000 + int negotiatedFPS = mVideoCallManager.getNegotiatedFPS() * MEDIA_TO_CAMERA_CONV_UNIT; + List fpsRangeList = mParameters.getSupportedPreviewFpsRange(); + + // Initialize bestFpsRange low and high values to 0 + int bestFpsLow = 0; + int bestFpsHigh = 0; + + for (int i = 0; i < fpsRangeList.size(); i++) { + int currFpsHigh = fpsRangeList.get(i)[1]; + int currFpsLow = fpsRangeList.get(i)[0]; + if (DBG) { + Log.d(LOG_TAG, "Supported FPS range = " + currFpsLow + " : " + + currFpsHigh); + } + + if (currFpsHigh != currFpsLow + && currFpsLow <= negotiatedFPS + && negotiatedFPS <= currFpsHigh) { + bestFpsLow = currFpsLow; + bestFpsHigh = negotiatedFPS; + break; + } + } + + if (!(bestFpsHigh == 0 && bestFpsLow == 0 )) { + if (DBG) { + Log.d(LOG_TAG, "Best FPS range for the negotiated FPS of " + negotiatedFPS + " is " + + bestFpsLow + " : " + bestFpsHigh); + } + mParameters.setPreviewFpsRange(bestFpsLow, bestFpsHigh); + } else { + Log.e(LOG_TAG, "Best FPS range for the negotiated FPS of " + negotiatedFPS + + " is not found"); + } + } + + /** + * This method resizes the camera preview based on the aspect ratio + * supported by camera and the size of VideoCallPanel + * + * @param targetSize + */ + private void resizeCameraPreview(int targetSize) { + if (DBG) log("resizeCameraPreview"); + + // For now, set the preview size to be 1/4th of the VideoCallPanel + mPreviewSize = mVideoCallManager.getCameraPreviewSize(targetSize / 4, true); + if (mPreviewSize != null) { + log("Camera view width:" + mPreviewSize.width + ", height:" + mPreviewSize.height); + ViewGroup.LayoutParams cameraPreivewLp = mCameraPreview.getLayoutParams(); + cameraPreivewLp.height = mPreviewSize.height; + cameraPreivewLp.width = mPreviewSize.width; + mCameraPreview.setLayoutParams(cameraPreivewLp); + } + } + + /** + * This method resizes the far end view based on the size of VideoCallPanel + * Presently supports only full size far end video + * @param targetWidth + * @param targetHeight + */ + private void resizeFarEndView(int targetWidth, int targetHeight) { + if (DBG) { + log("resizeFarEndView"); + log("Videocall pandel width:" + targetWidth + ", height:" + targetHeight); + } + + ViewGroup.LayoutParams farEndViewLp = mFarEndView.getLayoutParams(); + farEndViewLp.height = targetHeight; + farEndViewLp.width = targetWidth; + mFarEndView.setLayoutParams(farEndViewLp); + } + + /** + * This method switches the camera to front/back or off + * @param cameraId + */ + private void switchCamera(int cameraId) { + // Change the camera Id + mCameraId = cameraId; + + // Stop camera preview if already running + if (mVideoCallManager.getCameraState() != CameraState.CAMERA_CLOSED) { + stopPreview(); + closeCamera(); + } + + // Restart camera if camera doesn't need to stay off + if (isCameraInitNeeded()) { + initializeCamera(); + } + } + + /** + * Choose the camera direction to the front camera if it is available. Else + * set the camera direction to the rear facing + */ + private void chooseCamera(boolean chooseFrontCamera) { + // Assuming that both cameras are available. + mCameraId = chooseFrontCamera ? mFrontCameraId : mBackCameraId; + + // The requested camera is not available. + if (mCameraId == CAMERA_UNKNOWN) { + // Reverse the above logic, this will choose the other camera. + mCameraId = !chooseFrontCamera ? mFrontCameraId : mBackCameraId; + } + + if (mCameraId == CAMERA_UNKNOWN) { + loge("chooseCamera " + chooseFrontCamera + " Both camera ids unknown"); + } + } + + private void log(String msg) { + Log.d(LOG_TAG, msg); + } + + private void loge(String msg) { + Log.e(LOG_TAG, msg); + } +} diff --git a/src/com/android/incallui/ZoomControl.java b/src/com/android/incallui/ZoomControl.java new file mode 100644 index 00000000..ce98279a --- /dev/null +++ b/src/com/android/incallui/ZoomControl.java @@ -0,0 +1,124 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * + * Copyright (C) 2011 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.os.Handler; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +/** + * A view that contains camera zoom control which could adjust the zoom in/out + * if the camera supports zooming. + */ +public abstract class ZoomControl extends RelativeLayout{ + protected ImageView mZoomIn; + protected ImageView mZoomOut; + protected ImageView mZoomSlider; + protected int mOrientation; + + public interface OnZoomChangedListener { + void onZoomValueChanged(int index); // only for immediate zoom + } + + // The interface OnZoomIndexChangedListener is used to inform the + // ZoomIndexBar about the zoom index change. The index position is between + // 0 (the index is zero) and 1.0 (the index is mZoomMax). + public interface OnZoomIndexChangedListener { + void onZoomIndexChanged(double indexPosition); + } + + protected int mZoomMax, mZoomIndex; + private OnZoomChangedListener mListener; + + private int mStep; + + public ZoomControl(Context context, AttributeSet attrs) { + super(context, attrs); + mZoomIn = addImageView(context, R.drawable.ic_zoom_in); + mZoomSlider = addImageView(context, R.drawable.ic_zoom_slider); + mZoomOut = addImageView(context, R.drawable.ic_zoom_out); + } + + public void startZoomControl() { + mZoomSlider.setPressed(true); + setZoomIndex(mZoomIndex); // Update the zoom index bar. + } + + protected ImageView addImageView(Context context, int iconResourceId) { + ImageView image = new ImageView(context); + image.setImageResource(iconResourceId); + addView(image); + return image; + } + + public void closeZoomControl() { + mZoomSlider.setPressed(false); + } + + public void setZoomMax(int zoomMax) { + mZoomMax = zoomMax; + + // Layout should be requested as the maximum zoom level is the key to + // show the correct zoom slider position. + requestLayout(); + } + + public void setOnZoomChangeListener(OnZoomChangedListener listener) { + mListener = listener; + } + + public void setZoomIndex(int index) { + if (index < 0 || index > mZoomMax) { + throw new IllegalArgumentException("Invalid zoom value:" + index); + } + mZoomIndex = index; + invalidate(); + } + + protected void setZoomStep(int step) { + mStep = step; + } + + // Called from ZoomControlBar to change the zoom level. + protected void performZoom(double zoomPercentage) { + int index = (int) (mZoomMax * zoomPercentage); + if (mZoomIndex == index) return; + changeZoomIndex(index); + } + + private boolean changeZoomIndex(int index) { + if (mListener != null) { + if (index > mZoomMax) index = mZoomMax; + if (index < 0) index = 0; + mListener.onZoomValueChanged(index); + mZoomIndex = index; + } + return true; + } + + @Override + public void setActivated(boolean activated) { + super.setActivated(activated); + mZoomIn.setActivated(activated); + mZoomOut.setActivated(activated); + } +} diff --git a/src/com/android/incallui/ZoomControlBar.java b/src/com/android/incallui/ZoomControlBar.java new file mode 100644 index 00000000..7d7597eb --- /dev/null +++ b/src/com/android/incallui/ZoomControlBar.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * + * Copyright (C) 2011 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.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * A view that contains camera zoom control and its layout. + */ +public class ZoomControlBar extends ZoomControl { + private static final int THRESHOLD_FIRST_MOVE = 10; // pixels + // Space between indicator icon and the zoom-in/out icon. + private static final int ICON_SPACING = 12; + + private View mBar; + private boolean mStartChanging; + private int mSliderPosition = 0; + private int mSliderLength; + private int mWidth; + private int mIconWidth; + private int mTotalIconWidth; + + public ZoomControlBar(Context context, AttributeSet attrs) { + super(context, attrs); + mBar = new View(context); + mBar.setBackgroundResource(R.drawable.zoom_slider_bar); + addView(mBar); + } + + @Override + public void setActivated(boolean activated) { + super.setActivated(activated); + mBar.setActivated(activated); + } + + private int getSliderPosition(int x) { + // Calculate the absolute offset of the slider in the zoom control bar. + // For left-hand users, as the device is rotated for 180 degree for + // landscape mode, the zoom-in bottom should be on the top, so the + // position should be reversed. + int pos; // the relative position in the zoom slider bar + if (mOrientation == 90) { + pos = mWidth - mTotalIconWidth - x; + } else { + pos = x - mTotalIconWidth; + } + if (pos < 0) pos = 0; + if (pos > mSliderLength) pos = mSliderLength; + return pos; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mWidth = w; + mIconWidth = mZoomIn.getMeasuredWidth(); + mTotalIconWidth = mIconWidth + ICON_SPACING; + mSliderLength = mWidth - (2 * mTotalIconWidth); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (!isEnabled() || (mWidth == 0)) return false; + int action = event.getAction(); + + switch (action) { + case MotionEvent.ACTION_OUTSIDE: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + setActivated(false); + closeZoomControl(); + break; + + case MotionEvent.ACTION_DOWN: + setActivated(true); + mStartChanging = false; + case MotionEvent.ACTION_MOVE: + int pos = getSliderPosition((int) event.getX()); + if (!mStartChanging) { + // Make sure the movement is large enough before we start + // changing the zoom. + int delta = mSliderPosition - pos; + if ((delta > THRESHOLD_FIRST_MOVE) || + (delta < -THRESHOLD_FIRST_MOVE)) { + mStartChanging = true; + } + } + if (mStartChanging) { + performZoom(1.0d * pos / mSliderLength); + mSliderPosition = pos; + } + requestLayout(); + } + return true; + } + + @Override + protected void onLayout( + boolean changed, int left, int top, int right, int bottom) { + if (mZoomMax == 0) return; + int height = bottom - top; + mBar.layout(mTotalIconWidth, 0, mWidth - mTotalIconWidth, height); + // For left-hand users, as the device is rotated for 180 degree, + // the zoom-in button should be on the top. + int pos; // slider position + int sliderPosition; + if (mSliderPosition != -1) { // -1 means invalid + sliderPosition = mSliderPosition; + } else { + sliderPosition = (int) ((double) mSliderLength * mZoomIndex / mZoomMax); + } + if (mOrientation == 90) { + mZoomIn.layout(0, 0, mIconWidth, height); + mZoomOut.layout(mWidth - mIconWidth, 0, mWidth, height); + pos = mBar.getRight() - sliderPosition; + } else { + mZoomOut.layout(0, 0, mIconWidth, height); + mZoomIn.layout(mWidth - mIconWidth, 0, mWidth, height); + pos = mBar.getLeft() + sliderPosition; + } + int sliderWidth = mZoomSlider.getMeasuredWidth(); + mZoomSlider.layout((pos - sliderWidth / 2), 0, + (pos + sliderWidth / 2), height); + } + + @Override + public void setZoomIndex(int index) { + super.setZoomIndex(index); + mSliderPosition = -1; // -1 means invalid + requestLayout(); + } +} -- cgit v1.2.3 From bb131b321afde0344d4c93759a91fabeaaf2b401 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Tatavarthi Date: Wed, 20 Nov 2013 18:33:23 -0800 Subject: IMS: Enable Modify Call functionality 1. Add ModifyCall api to CallCommandClinet 2. Add ModifyCallConfirm api to CallCommandClient 3. Add onCallModify api to CallHandlerService Change-Id: I3b37373be9161572dcd7a23368f3a644e28936e5 --- src/com/android/incallui/CallCommandClient.java | 26 ++++++++++++++++++++++++ src/com/android/incallui/CallHandlerService.java | 13 ++++++++++++ 2 files changed, 39 insertions(+) (limited to 'src') diff --git a/src/com/android/incallui/CallCommandClient.java b/src/com/android/incallui/CallCommandClient.java index 895a7296..7505626f 100644 --- a/src/com/android/incallui/CallCommandClient.java +++ b/src/com/android/incallui/CallCommandClient.java @@ -259,6 +259,32 @@ public class CallCommandClient { } } + public void modifyCallInitiate(int callId) { + if (mCommandService == null) { + Log.e(this, "Cannot modifyCall(); CallCommandService == null"); + return; + } + try { + Log.v(this, "modifyCall() "); + mCommandService.modifyCallInitiate(callId); + } catch (RemoteException e) { + Log.e(this, "Error on modifyCall()."); + } + } + + public void modifyCallConfirm(boolean responseType, int callId) { + if (mCommandService == null) { + Log.e(this, "Cannot modifyCallConfirm(); CallCommandService == null" + responseType); + return; + } + try { + Log.v(this, "modifyCallConfirm() "); + mCommandService.modifyCallConfirm(responseType, callId); + } catch (RemoteException e) { + Log.e(this, "Error on modifyCallConfirm()."); + } + } + public void setSystemBarNavigationEnabled(boolean enable) { if (mCommandService == null) { Log.e(this, "Cannot setSystemBarNavigationEnabled(); CallCommandService == null"); diff --git a/src/com/android/incallui/CallHandlerService.java b/src/com/android/incallui/CallHandlerService.java index 8d2b1aa6..a4825c6d 100644 --- a/src/com/android/incallui/CallHandlerService.java +++ b/src/com/android/incallui/CallHandlerService.java @@ -49,6 +49,7 @@ public class CallHandlerService extends Service { private static final int ON_START = 9; private static final int ON_DESTROY = 10; private static final int ON_ACTIVE_SUB_CHANGE = 11; + private static final int ON_UNSOL_CALLMODIFY = 12; private static final int LARGEST_MSG_ID = ON_DESTROY; @@ -188,6 +189,16 @@ public class CallHandlerService extends Service { chars)); } + @Override + public void onModifyCall(Call call) { + try { + Log.i(TAG, "onModifyCallResponse: " + call); + mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UNSOL_CALLMODIFY, call)); + } catch (Exception e) { + Log.e(TAG, "Error processing onDisconnect() call.", e); + } + } + @Override public void onActiveSubChanged(int activeSub) { mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_ACTIVE_SUB_CHANGE, activeSub)); @@ -311,6 +322,8 @@ public class CallHandlerService extends Service { case ON_DESTROY: doStop(); break; + case ON_UNSOL_CALLMODIFY: + break; case ON_ACTIVE_SUB_CHANGE: Log.i(TAG, "ON_ACTIVE_SUB_CHANGE: " + msg.obj); mCallList.onActiveSubChanged((Integer) msg.obj); -- cgit v1.2.3 From d2c9f3fe93e565f133750ee4f88f1c3c0bce9e19 Mon Sep 17 00:00:00 2001 From: Rekha Kumar Date: Tue, 5 Nov 2013 14:10:59 -0800 Subject: IMS-VT: Recording Buffer and CVO Features Following changes are included: -Use Recording buffer architecture for camera -JNI support for media library camera APIs -Move existing camera API calls to IMS Camera API calls -CVO - Coordinated Video Orientation feature In CVO mode, whenever the receiver side device is rotated, use sensor orientation information to rotate the far end surface, so that co-ordinate system of surface always has (0,0) fixed with respect to earth -CVO: Send initial orientation on CVO start. Change-Id: I5c31be394f10e344602c279c8927ef430e23a3b4 --- src/com/android/incallui/CallCardFragment.java | 2 + src/com/android/incallui/CameraHandler.java | 123 ++++--------- src/com/android/incallui/CvoHandler.java | 237 +++++++++++++++++++++++++ src/com/android/incallui/ImsCamera.java | 180 +++++++++++++++++++ src/com/android/incallui/MediaHandler.java | 104 +++++++++-- src/com/android/incallui/VideoCallManager.java | 153 +++++++++------- src/com/android/incallui/VideoCallPanel.java | 187 ++++++++----------- 7 files changed, 703 insertions(+), 283 deletions(-) create mode 100644 src/com/android/incallui/CvoHandler.java create mode 100644 src/com/android/incallui/ImsCamera.java (limited to 'src') diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index 4c2f4ef1..077538a4 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -638,6 +638,7 @@ public class CallCardFragment extends BaseFragment @@ -50,22 +47,26 @@ import java.util.List; * {@code android.hardware.Camera}. */ -public class CameraHandler implements Camera.PreviewCallback{ +public class CameraHandler { public static final int CAMERA_UNKNOWN = -1; private static final String TAG = "VideoCallCameraHandler"; private static final boolean DBG = true; - private android.hardware.Camera mCameraDevice; + private ImsCamera mCameraDevice; private int mNumberOfCameras; private int mCameraId = CAMERA_UNKNOWN; // current camera id private int mBackCameraId = CAMERA_UNKNOWN, mFrontCameraId = CAMERA_UNKNOWN; private CameraInfo[] mInfo; private CameraState mCameraState = CameraState.CAMERA_CLOSED; - private Parameters mParameters; private Context mContext; // Use a singleton. private static CameraHandler mInstance; + // Check if device policy has disabled the camera. + private DevicePolicyManager mDpm; + // Get display rotation + WindowManager mWindowManager; + /** * Enum that defines the various camera states */ @@ -109,6 +110,11 @@ public class CameraHandler implements Camera.PreviewCallback{ log("Front camera ID is: " + mFrontCameraId); } } + mDpm = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + // Get display rotation + mWindowManager = (WindowManager) mContext.getSystemService( + Context.WINDOW_SERVICE); } /** @@ -129,14 +135,11 @@ public class CameraHandler implements Camera.PreviewCallback{ */ public synchronized boolean open(int cameraId) throws Exception { - // Check if device policy has disabled the camera. - DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( - Context.DEVICE_POLICY_SERVICE); - if (dpm == null) { + if (mDpm == null) { throw new Exception("DevicePolicyManager not available"); } - if (dpm.getCameraDisabled(null)) { + if (mDpm.getCameraDisabled(null)) { throw new Exception("Camera is disabled"); } @@ -148,21 +151,12 @@ public class CameraHandler implements Camera.PreviewCallback{ if (mCameraDevice == null) { try { if (DBG) log("opening camera " + cameraId); - mCameraDevice = android.hardware.Camera.open(cameraId); + mCameraDevice = ImsCamera.open(cameraId); mCameraId = cameraId; - } catch (RuntimeException e) { + } catch (Exception e) { loge("fail to connect Camera" + e); - throw new Exception(e); + throw e; } - mParameters = mCameraDevice.getParameters(); - } else { - try { - mCameraDevice.reconnect(); - } catch (IOException e) { - loge("reconnect failed."); - throw new Exception(e); - } - setCameraParameters(mParameters); } mCameraState = CameraState.PREVIEW_STOPPED; return true; @@ -186,9 +180,6 @@ public class CameraHandler implements Camera.PreviewCallback{ // Set the SurfaceTexture to be used for preview mCameraDevice.setPreviewTexture(mSurfaceTexture); - // Set the Preview Call Back to show the camera frames on UI - mCameraDevice.setPreviewCallback(this); - setDisplayOrientation(); mCameraDevice.startPreview(); mCameraState = CameraState.PREVIEW_STARTED; @@ -207,11 +198,12 @@ public class CameraHandler implements Camera.PreviewCallback{ if (mCameraDevice != null) { if (DBG) log("closing camera"); - mCameraDevice.stopPreview(); // Stop preview + if (mCameraState == CameraState.PREVIEW_STARTED) { + mCameraDevice.stopPreview(); + } mCameraDevice.release(); } mCameraDevice = null; - mParameters = null; mCameraId = CAMERA_UNKNOWN; mCameraState = CameraState.CAMERA_CLOSED; } @@ -228,37 +220,21 @@ public class CameraHandler implements Camera.PreviewCallback{ } if (mCameraDevice != null) { if (DBG) log("stopping preview"); - mCameraDevice.setPreviewCallback(null); mCameraDevice.stopPreview(); } mCameraState = CameraState.PREVIEW_STOPPED; } - /** - * Return the camera parameters that specifies the current settings of the - * camera - * - * @return camera parameters - */ - public Parameters getCameraParameters() { - if (mCameraDevice == null) { - return null; + public void startCameraRecording() { + if (mCameraDevice != null && mCameraState == CameraState.PREVIEW_STARTED) { + mCameraDevice.startRecording(); } - return mParameters; } - /** - * Set the camera parameters - * - * @param parameters to be set - */ - public void setCameraParameters(Parameters parameters) { - log("setCameraParameters mCameraDevice=" + mCameraDevice + "parameters =" + parameters); - if (mCameraDevice == null || parameters == null) { - return; + public void stopCameraRecording() { + if (mCameraDevice != null) { + mCameraDevice.stopRecording(); } - mParameters = parameters; - mCameraDevice.setParameters(parameters); } /** @@ -296,11 +272,8 @@ public class CameraHandler implements Camera.PreviewCallback{ public void setDisplay(SurfaceTexture surfaceTexture) { // Set the SurfaceTexture to be used for preview if (mCameraDevice == null) return; - try { - mCameraDevice.setPreviewTexture(surfaceTexture); - } catch (IOException e) { - throw new RuntimeException("setPreviewDisplay failed", e); - } + mCameraDevice.setPreviewTexture(surfaceTexture); + } /** @@ -309,24 +282,7 @@ public class CameraHandler implements Camera.PreviewCallback{ * @param textureView */ public void setDisplay(TextureView textureView) { - // Set the SurfaceTexture to be used for preview - if (mCameraDevice == null) return; - try { - mCameraDevice.setPreviewTexture(textureView.getSurfaceTexture()); - } catch (IOException e) { - throw new RuntimeException("setPreviewDisplay failed", e); - } - } - - /** - * Gets the supported preview sizes. - * - * @return a list of Size object. This method will always return a list - * with at least one element. - */ - public List getSupportedPreviewSizes() { - if (mCameraDevice == null) return null; - return mCameraDevice.getParameters().getSupportedPreviewSizes(); + setDisplay(textureView.getSurfaceTexture()); } /** @@ -355,15 +311,12 @@ public class CameraHandler implements Camera.PreviewCallback{ int degrees = 0; int rotation = 0; - // Get display rotation - WindowManager wm = (WindowManager) mContext.getSystemService( - Context.WINDOW_SERVICE); - if (wm == null) { + if (mWindowManager == null) { loge("WindowManager not available"); return; } - rotation = wm.getDefaultDisplay().getRotation(); + rotation = mWindowManager.getDefaultDisplay().getRotation(); switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; @@ -383,14 +336,8 @@ public class CameraHandler implements Camera.PreviewCallback{ mCameraDevice.setDisplayOrientation(result); } - /** - * Called as preview frames are displayed. The frames are passed to IMS DPL - * layer to be sent to the far end device - */ - public void onPreviewFrame(byte[] data, Camera camera) { - if (MediaHandler.canSendPreview()) { - MediaHandler.sendPreviewFrame(data); - } + public ImsCamera getImsCameraInstance() { + return mCameraDevice; } private void log(String msg) { diff --git a/src/com/android/incallui/CvoHandler.java b/src/com/android/incallui/CvoHandler.java new file mode 100644 index 00000000..c6968352 --- /dev/null +++ b/src/com/android/incallui/CvoHandler.java @@ -0,0 +1,237 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.content.Context; +import android.hardware.SensorManager; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Registrant; +import android.os.RegistrantList; +import android.util.Log; +import android.view.OrientationEventListener; +import android.view.WindowManager; + +/** + * Provides an interface to handle the CVO - Coordinated Video Orientation part + * of the video telephony call + */ +public class CvoHandler extends Handler { + + private static final String TAG = "VideoCall_CvoHandler"; + private static final boolean DBG = true; + + private static final int ORIENTATION_ANGLE_0 = 0; + private static final int ORIENTATION_ANGLE_90 = 1; + private static final int ORIENTATION_ANGLE_180 = 2; + private static final int ORIENTATION_ANGLE_270 = 3; + private static final int ORIENTATION_MODE_THRESHOLD = 45; + + /** + * Phone orientation angle which can take one of the 4 values + * ORIENTATION_ANGLE_0, ORIENTATION_ANGLE_90, ORIENTATION_ANGLE_180, + * ORIENTATION_ANGLE_270 + */ + private int mCurrentOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; + private RegistrantList mCvoRegistrants = new RegistrantList(); + + private Context mContext; + private WindowManager mWindowManager; // Used to get display rotation. + + private OrientationEventListener mOrientationEventListener; + + public CvoHandler(Context context) { + mContext = context; + + mOrientationEventListener = createOrientationListener(); + startOrientationListener(false); + + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + log("CvoHandler created"); + } + + + public interface CvoEventListener { + /** + * This callback method will be invoked when the device orientation changes. + */ + void onDeviceOrientationChanged(int rotation); + } + + /** + * Register for CVO device orientation changed event + */ + public void registerForCvoInfoChange(Handler h, int what, Object obj) { + log("registerForCvoInfoChange handler= " + h + " what= " + what + " obj= " + obj); + Registrant r = new Registrant(h, what, obj); + mCvoRegistrants.add(r); + } + + public void unregisterForCvoInfoChange(Handler h) { + log("unregisterForCvoInfoChange handler= " + h); + mCvoRegistrants.remove(h); + } + + /** + * Enable sensor to listen for device orientation changes + */ + public void startOrientationListener(boolean start) { + log("startOrientationListener " + start); + if (start) { + if (mOrientationEventListener.canDetectOrientation()) { + notifyInitialOrientation(); + mOrientationEventListener.enable(); + } else { + log("Cannot detect orientation"); + } + } else { + mOrientationEventListener.disable(); + mCurrentOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; + } + } + + /* Protected to facilitate unittesting.*/ + protected void doOnOrientationChanged(int angle) { + final int newOrientation = calculateDeviceOrientation(angle); + if (hasDeviceOrientationChanged(newOrientation)) { + notifyCvoClient(newOrientation); + } + } + + /** + * For CVO mode handling, phone is expected to have only 4 orientations The + * orientation sensor gives every degree change angle. This needs to be + * categorized to one of the 4 angles. This method does this calculation. + * @param angle + * @return one of the 4 orientation angles ORIENTATION_ANGLE_0, + * ORIENTATION_ANGLE_90, ORIENTATION_ANGLE_180, + * ORIENTATION_ANGLE_270 + */ + protected static int calculateDeviceOrientation(int angle) { + int newOrientation = ORIENTATION_ANGLE_0; + if ((angle >= 0 + && angle < 0 + ORIENTATION_MODE_THRESHOLD) || + (angle >= 360 - ORIENTATION_MODE_THRESHOLD && + angle < 360)) { + newOrientation = ORIENTATION_ANGLE_0; + } else if (angle >= 90 - ORIENTATION_MODE_THRESHOLD + && angle < 90 + ORIENTATION_MODE_THRESHOLD) { + newOrientation = ORIENTATION_ANGLE_90; + } else if (angle >= 180 - ORIENTATION_MODE_THRESHOLD + && angle < 180 + ORIENTATION_MODE_THRESHOLD) { + newOrientation = ORIENTATION_ANGLE_180; + } else if (angle >= 270 - ORIENTATION_MODE_THRESHOLD + && angle < 270 + ORIENTATION_MODE_THRESHOLD) { + newOrientation = ORIENTATION_ANGLE_270; + } + return newOrientation; + } + + /** + * Detect change in device orientation + */ + private boolean hasDeviceOrientationChanged(int newOrientation) { + if (DBG) { + log("hasDeviceOrientationChanged mCurrentOrientation= " + + mCurrentOrientation + " newOrientation= " + newOrientation); + } + if (newOrientation != mCurrentOrientation) { + mCurrentOrientation = newOrientation; + return true; + } + return false; + } + + /** + * Send newOrientation to client + */ + private void notifyCvoClient(int newOrientation) { + AsyncResult ar = new AsyncResult(null, mCurrentOrientation, null); + mCvoRegistrants.notifyRegistrants(ar); + } + + static public int convertMediaOrientationToActualAngle(int newOrientation) { + int angle = 0; + switch (newOrientation) { + case ORIENTATION_ANGLE_0: + angle = 0; + break; + case ORIENTATION_ANGLE_90: + angle = 90; + break; + case ORIENTATION_ANGLE_180: + angle = 180; + break; + case ORIENTATION_ANGLE_270: + angle = 270; + break; + default: + loge("getAngleFromOrientation: Undefined orientation"); + } + return angle; + } + + private void notifyInitialOrientation() { + final int angle = getCurrentOrientation(); + log("Current orientation is: " + angle); + if ( angle != OrientationEventListener.ORIENTATION_UNKNOWN ) { + doOnOrientationChanged( convertMediaOrientationToActualAngle(angle) ); + } else { + log("Initial orientation is ORIENTATION_UNKNOWN"); + } + } + + private int getCurrentOrientation() { + if (mWindowManager != null) { + return mWindowManager.getDefaultDisplay().getRotation(); + } else { + loge("WindowManager not available."); + return OrientationEventListener.ORIENTATION_UNKNOWN; + } + } + + private OrientationEventListener createOrientationListener() { + return new OrientationEventListener( + mContext, SensorManager.SENSOR_DELAY_NORMAL) { + @Override + public void onOrientationChanged(int angle) { + doOnOrientationChanged(angle); + } + }; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } + + private static void loge(String msg) { + Log.e(TAG, msg); + } + +} diff --git a/src/com/android/incallui/ImsCamera.java b/src/com/android/incallui/ImsCamera.java new file mode 100644 index 00000000..de8d9ee3 --- /dev/null +++ b/src/com/android/incallui/ImsCamera.java @@ -0,0 +1,180 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.incallui; + +import android.graphics.SurfaceTexture; +import android.util.Log; + +/** + * The class is used to hold an {@code android.hardware.Camera} instance. + *

+ * The {@code open()} and {@code release()} calls are similar to the ones in + * {@code android.hardware.Camera}. + */ + +public class ImsCamera { + private static final String TAG = "VideoCallImsCamera"; + private static final boolean DBG = true; + private static final short IMS_CAMERA_OPERATION_SUCCESS = 0; + + static { + System.loadLibrary("imscamera_jni"); + } + + public static native short native_open(int cameraId); + + public native short native_release(); + + public native short native_startPreview(); + + public native short native_stopPreview(); + + public native short native_startRecording(); + + public native short native_stopRecording(); + + public native short native_setPreviewTexture(SurfaceTexture st); + + public native short native_setDisplayOrientation(int rotation); + + public native boolean native_isZoomSupported(); + + public native int native_getMaxZoom(); + + public native void native_setZoom(int zoomValue); + + public native short native_setPreviewSize(int width, int height); + + public native short native_setPreviewFpsRange(short fps); + + public static ImsCamera open(int cameraId) throws Exception { + Log.d(TAG, "open cameraId=" + cameraId); + short error = native_open(cameraId); + if (error != IMS_CAMERA_OPERATION_SUCCESS) { + Log.e(TAG, "open cameraId=" + cameraId + " failed with error=" + error); + throw new Exception(); + } else { + return new ImsCamera(); + } + } + + public short release() { + if(DBG) log("release"); + short error = native_release(); + logIfError("release", error); + return error; + } + + public short startPreview() { + if (DBG) log("startPreview"); + short error = native_startPreview(); + logIfError("startPreview", error); + return error; + } + + public short stopPreview() { + if(DBG) log("stopPreview"); + short error = native_stopPreview(); + logIfError("stopPreview", error); + return error; + } + + public short startRecording() { + if(DBG) log("startRecording"); + short error = native_startRecording(); + logIfError("startRecording", error); + return error; + } + + public short stopRecording() { + if(DBG) log("stopRecording"); + short error = native_stopRecording(); + logIfError("stopRecording", error); + return error; + } + + public short setPreviewTexture(SurfaceTexture st) { + if(DBG) log("setPreviewTexture"); + short error = native_setPreviewTexture(st); + logIfError("setPreviewTexture", error); + return error; + } + + public short setDisplayOrientation(int rotation) { + if(DBG) log("setDisplayOrientation rotation=" + rotation); + short error = native_setDisplayOrientation(rotation); + logIfError("setDisplayOrientation", error); + return error; + } + + public boolean isZoomSupported() { + boolean result = native_isZoomSupported(); + if(DBG) log("isZoomSupported result=" + result); + return result; + } + + public int getMaxZoom() { + int result = native_getMaxZoom(); + if(DBG) log("getMaxZoom result = " + result); + return result; + } + + public void setZoom(int zoomValue) { + if (DBG) log("setZoom " + zoomValue); + native_setZoom(zoomValue); + } + + public short setPreviewSize(int width, int height) { + if(DBG) log("setPreviewSize"); + short error = native_setPreviewSize(width, height); + logIfError("setPreviewSize", error); + return error; + } + + public short setPreviewFpsRange(short fps) { + if(DBG) log("setPreviewFpsRange"); + short error = native_setPreviewFpsRange(fps); + logIfError("setPreviewFpsRange", error); + return error; + } + + private void log(String msg) { + Log.d(TAG, msg); + } + + private void loge(String msg) { + Log.e(TAG, msg); + } + + private void logIfError(String methodName, short error) { + if (error != IMS_CAMERA_OPERATION_SUCCESS) { + loge(methodName + " failed with error=" + error); + } + } +} diff --git a/src/com/android/incallui/MediaHandler.java b/src/com/android/incallui/MediaHandler.java index 78642972..c04d565f 100644 --- a/src/com/android/incallui/MediaHandler.java +++ b/src/com/android/incallui/MediaHandler.java @@ -28,8 +28,13 @@ package com.android.incallui; +import android.content.pm.ActivityInfo; import android.graphics.SurfaceTexture; +import android.os.AsyncResult; import android.os.Handler; +import android.os.Message; +import android.os.Registrant; +import android.os.RegistrantList; import android.util.Log; /** @@ -51,14 +56,24 @@ public class MediaHandler extends Handler { private static native void nativeDeInit(); private static native void nativeHandleRawFrame(byte[] frame); private static native int nativeSetSurface(SurfaceTexture st); + private static native void nativeSetDeviceOrientation(int orientation); private static native short nativeGetNegotiatedFPS(); private static native int nativeGetNegotiatedHeight(); private static native int nativeGetNegotiatedWidth(); + private static native int nativeGetUIOrientationMode(); private static native void nativeRegisterForMediaEvents(MediaHandler instance); public static final int PARAM_READY_EVT = 1; public static final int START_READY_EVT = 2; + public static final int DISPLAY_MODE_EVT = 5; + protected final RegistrantList mDisplayModeEventRegistrants + = new RegistrantList(); + + // UI Orientation Modes + private static final int LANDSCAPE_MODE = 1; + private static final int PORTRAIT_MODE = 2; + private static final int CVO_MODE = 3; /* * Initializing default negotiated parameters to a working set of valuesso * that the application does not crash in case we do not get the Param ready @@ -66,14 +81,35 @@ public class MediaHandler extends Handler { */ private static int mNegotiatedHeight = 240; private static int mNegotiatedWidth = 320; + private static int mUIOrientationMode = PORTRAIT_MODE; private static short mNegotiatedFps = 20; private MediaEventListener mMediaEventListener; + public RegistrantList mCvoModeOnRegistrant = new RegistrantList(); + + // Use a singleton + private static MediaHandler mInstance; + + /** + * This method returns the single instance of MediaHandler object * + */ + public static synchronized MediaHandler getInstance() { + if (mInstance == null) { + mInstance = new MediaHandler(); + } + return mInstance; + } - private static boolean mIsReadyToReceivePreview = false; + /** + * Private constructor for MediaHandler + */ + private MediaHandler() { + } public interface MediaEventListener { void onParamReadyEvent(); + void onDisplayModeEvent(); + void onStartReadyEvent(); } static { @@ -89,8 +125,6 @@ public class MediaHandler extends Handler { */ public int init() { if (!mInitCalledFlag) { - //Initialize mIsReadyToReceivePreview to false to begin with - mIsReadyToReceivePreview = false; int error = nativeInit(); Log.d(TAG, "init called error = " + error); switch (error) { @@ -123,6 +157,11 @@ public class MediaHandler extends Handler { mInitCalledFlag = false; } + public void sendCvoInfo(int orientation) { + Log.d(TAG, "sendCvoInfo orientation=" + orientation); + nativeSetDeviceOrientation(orientation); + } + /** * Send the camera preview frames to the media module to be sent to the far * end party @@ -155,14 +194,6 @@ public class MediaHandler extends Handler { nativeSetSurface(mSurface); } - /** - * Get Negotiated FPS - */ - public static short getNegotiatedFPS() { - Log.d(TAG, "Negotiated FPS = " + mNegotiatedFps); - return mNegotiatedFps; - } - /** * Get Negotiated Height */ @@ -179,13 +210,16 @@ public class MediaHandler extends Handler { return mNegotiatedWidth; } - public static synchronized boolean canSendPreview() { - return MediaHandler.mIsReadyToReceivePreview; + /** + * Get Negotiated Width + */ + public int getUIOrientationMode() { + Log.d(TAG, "UI Orientation Mode = " + mUIOrientationMode); + return mUIOrientationMode; } - public static synchronized void setIsReadyToReceivePreview(boolean flag) { - Log.d(TAG, "setIsReadyToReceivePreview = " + flag); - MediaHandler.mIsReadyToReceivePreview = flag; + public static short getNegotiatedFps() { + return mNegotiatedFps; } /** @@ -218,11 +252,47 @@ public class MediaHandler extends Handler { break; case START_READY_EVT: Log.d(TAG, "Received START_READY_EVT. Camera frames can be sent now"); - setIsReadyToReceivePreview(true); + if (mMediaEventListener != null) { + mMediaEventListener.onStartReadyEvent(); + } + break; + case DISPLAY_MODE_EVT: + mUIOrientationMode = nativeGetUIOrientationMode(); + processUIOrientationMode(); + if (mMediaEventListener != null) { + mMediaEventListener.onDisplayModeEvent(); + } break; default: Log.e(TAG, "Received unknown event id=" + eventId); } } + + private void processUIOrientationMode() { + mCvoModeOnRegistrant.notifyRegistrants(new AsyncResult(null, + isCvoModeEnabled(), null)); + } + + /** + * Register for mode change notification from IMS media library to determine + * if CVO mode needs to be activated or deactivated + */ + public void registerForCvoModeRequestChanged(Handler h, int what, Object obj) { + Registrant r = new Registrant(h, what, obj); + mCvoModeOnRegistrant.add(r); + } + + /** + * TODO Call all unregister methods Unregister for mode change notification + * from IMS media library to determine if CVO mode needs to be activated or + * deactivated + */ + public void unregisterForCvoModeRequestChanged(Handler h) { + mCvoModeOnRegistrant.remove(h); + } + + public boolean isCvoModeEnabled() { + return mUIOrientationMode == CVO_MODE; + } } diff --git a/src/com/android/incallui/VideoCallManager.java b/src/com/android/incallui/VideoCallManager.java index ea4ffd4b..8a8c08eb 100644 --- a/src/com/android/incallui/VideoCallManager.java +++ b/src/com/android/incallui/VideoCallManager.java @@ -28,16 +28,16 @@ package com.android.incallui; -import java.io.IOException; - import android.content.Context; import android.graphics.SurfaceTexture; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; import android.util.Log; import com.android.incallui.CameraHandler.CameraState; +import com.android.incallui.CvoHandler.CvoEventListener; -import java.util.List; +import java.io.IOException; /** * Provides an interface for the applications to interact with Camera for the @@ -49,14 +49,54 @@ public class VideoCallManager { private static VideoCallManager mInstance; // Use a singleton private CameraHandler mCameraHandler; private MediaHandler mMediaHandler; + private CvoHandler mCvoHandler; + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + AsyncResult ar; + switch (msg.what) { + case CVO_MODE_REQUEST_CHANGED: + ar = (AsyncResult) msg.obj; + if (ar != null && ar.result != null && ar.exception == null) { + boolean start = (Boolean) ar.result; + mCvoHandler.startOrientationListener(start); + } + break; + case CVO_INFO_CHANGED: + ar = (AsyncResult) msg.obj; + if (ar != null && ar.result != null && ar.exception == null) { + int orientation = (Integer) ar.result; + mMediaHandler.sendCvoInfo(orientation); + notifyCvoClient(orientation); + } + break; + } + } + }; + private CvoEventListener mCvoEventListener; + + private static final int CVO_MODE_REQUEST_CHANGED = 0; + private static final int CVO_INFO_CHANGED = 2; /** @hide */ private VideoCallManager(Context context) { log("Instantiating VideoCallManager"); mCameraHandler = CameraHandler.getInstance(context); - mMediaHandler = new MediaHandler(); + mMediaHandler = MediaHandler.getInstance(); + mCvoHandler = new CvoHandler(context); + mMediaHandler.registerForCvoModeRequestChanged(mHandler, CVO_MODE_REQUEST_CHANGED, null); + mCvoHandler.registerForCvoInfoChange(mHandler, CVO_INFO_CHANGED, null); } + private void notifyCvoClient(int orientation) { + int angle = mCvoHandler + .convertMediaOrientationToActualAngle(orientation); + log("handleMessage Device orientation angle=" + angle); + if (mCvoEventListener != null) { + mCvoEventListener.onDeviceOrientationChanged(angle); + } + } + /** * This method returns the single instance of VideoCallManager object * @@ -128,18 +168,21 @@ public class VideoCallManager { } /** - * Get negotiated FPS + * Get UI Orientation mode */ - public int getNegotiatedFPS() { - return MediaHandler.getNegotiatedFPS(); + public int getUIOrientationMode() { + return mMediaHandler.getUIOrientationMode(); } - public static boolean isMediaReadyToReceivePreview() { - return MediaHandler.canSendPreview(); + /** + * Get negotiated FPS + */ + public short getNegotiatedFps() { + return MediaHandler.getNegotiatedFps(); } - public static void setIsMediaReadyToReceivePreview(boolean flag) { - MediaHandler.setIsReadyToReceivePreview(flag); + public boolean isCvoModeEnabled() { + return mMediaHandler.isCvoModeEnabled(); } /** @@ -187,25 +230,6 @@ public class VideoCallManager { mCameraHandler.stopPreview(); } - /** - * Return the camera parameters that specifies the current settings of the - * camera - * - * @return camera parameters - */ - public Parameters getCameraParameters() { - return mCameraHandler.getCameraParameters(); - } - - /** - * Set the camera parameters - * - * @param parameters to be set - */ - public void setCameraParameters(Parameters parameters) { - mCameraHandler.setCameraParameters(parameters); - } - /** * Get the camera ID for the back camera * @@ -242,43 +266,6 @@ public class VideoCallManager { mCameraHandler.setDisplay(surfaceTexture); } - /** - * Gets the camera preview size that matches the given width or height to - * preserve the aspect ratio. - * - * @param size specifies height or width of the camera surface - * @param isHeight specifies if the given size is height if true or width - * if false - * @return width and height of camera preview as a Point - * point.x = width - * point.y = height - */ - public Size getCameraPreviewSize(int targetSize, boolean isHeight) { - double minDiff = Double.MAX_VALUE; - Size optimalSize = null; - - List mSupportedPreviewSizes = mCameraHandler.getSupportedPreviewSizes(); - if (mSupportedPreviewSizes == null) return null; // Camera not yet open - - // Try to find a size that matches closely with the required height or - // width - for (Size size : mSupportedPreviewSizes) { - int srcSize = 0; - if (isHeight) { - srcSize = size.height; - } - else { - srcSize = size.width; - } - - if (Math.abs(srcSize - targetSize) < minDiff) { - optimalSize = size; - minDiff = Math.abs(srcSize - targetSize); - } - } - return optimalSize; - } - /** * Returns the direction of the currently open camera * @@ -299,10 +286,38 @@ public class VideoCallManager { mCameraHandler.setDisplayOrientation(); } + public ImsCamera getImsCameraInstance() { + return mCameraHandler.getImsCameraInstance(); + } + + public void startCameraRecording() { + mCameraHandler.startCameraRecording(); + } + + public void stopCameraRecording() { + mCameraHandler.stopCameraRecording(); + } + public void setOnParamReadyListener(VideoCallPanel.ParamReadyListener listener) { mMediaHandler.setMediaEventListener(listener); } + /* + * Setup a CVO Event listener for triggering UI callbacks like + * onDeviceOrientationChanged to be invoked directly + */ + public void setCvoEventListener(CvoEventListener listener) { + log("setCvoEventListener"); + // TODO: Create a list of listeners or do not allow over-write + mCvoEventListener = listener; + } + + public void startOrientationListener(boolean start) { + if (isCvoModeEnabled()) { + mCvoHandler.startOrientationListener(start); + } + } + private void log(String msg) { Log.d(TAG, msg); } diff --git a/src/com/android/incallui/VideoCallPanel.java b/src/com/android/incallui/VideoCallPanel.java index 2448c49e..b15a4d46 100644 --- a/src/com/android/incallui/VideoCallPanel.java +++ b/src/com/android/incallui/VideoCallPanel.java @@ -28,12 +28,9 @@ package com.android.incallui; -import android.app.Activity; import android.content.Context; import android.graphics.SurfaceTexture; import android.hardware.Camera; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; import android.os.SystemProperties; import android.util.AttributeSet; import android.util.Log; @@ -47,17 +44,19 @@ import com.android.incallui.CameraHandler.CameraState; import com.android.services.telephony.common.CallDetails; import java.io.IOException; -import java.util.List; /** * Helper class to initialize and run the InCallScreen's "Video Call" UI. */ public class VideoCallPanel extends RelativeLayout implements TextureView.SurfaceTextureListener, View.OnClickListener { + private static final int LOOPBACK_MODE_HEIGHT = 144; + private static final int LOOPBACK_MODE_WIDTH = 176; private static final int CAMERA_UNKNOWN = -1; private static final String LOG_TAG = "VideoCallPanel"; private static final boolean DBG = true; private static final int MEDIA_TO_CAMERA_CONV_UNIT = 1000; + private static final int DEFAULT_CAMERA_ZOOM_VALUE = 0; private Context mContext; private VideoCallManager mVideoCallManager; @@ -71,11 +70,8 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac private SurfaceTexture mFarEndSurface; private ImageView mCameraPicker; - // Camera related - private Parameters mParameters; private int mZoomMax; private int mZoomValue; // The current zoom value - Size mPreviewSize; // Multiple cameras support private int mNumberOfCameras; @@ -112,15 +108,39 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac // If camera is already capturing stop preview, reset the // parameters and then start preview again try { + mVideoCallManager.stopCameraRecording(); mVideoCallManager.stopCameraPreview(); initializeCameraParams(); mVideoCallManager.startCameraPreview(mCameraSurface); + mVideoCallManager.startCameraRecording(); } catch (IOException ioe) { loge("Exception onParamReadyEvent stopping and starting preview " + ioe.toString()); } } + } + @Override + public void onDisplayModeEvent() { + // NO-OP + } + + @Override + public void onStartReadyEvent() { + // NO-OP + } + + } + + public class CvoListener implements CvoHandler.CvoEventListener { + @Override + public void onDeviceOrientationChanged(int rotation) { + int requiredSurfaceRotation = 360 - rotation; + if (DBG) { + log("onDeviceOrientationChanged: Local sensor rotation =" + rotation + + " Rotate far end based on local sensor by " + requiredSurfaceRotation); + } + mFarEndView.setRotation(requiredSurfaceRotation); } } @@ -181,6 +201,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac // Set media event listener mVideoCallManager.setOnParamReadyListener(new ParamReadyListener()); + mVideoCallManager.setCvoEventListener(new CvoListener()); } public void setCameraNeeded(boolean mCameraNeeded) { @@ -251,12 +272,16 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } initializeZoom(); initializeCameraParams(); - // Start camera preview - startPreview(); + startPreviewAndRecording(); } public boolean isCameraInitNeeded() { - return mCameraNeeded + if (DBG) { + log("isCameraInitNeeded mCameraNeeded=" + mCameraNeeded + " mCameraSurface= " + + mCameraSurface + " camera state = " + + mVideoCallManager.getCameraState()); + } + return mCameraNeeded && mCameraSurface != null && mVideoCallManager.getCameraState() == CameraState.CAMERA_CLOSED; } @@ -278,30 +303,32 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } /** - * This method starts the camera preview + * This method disconnect and releases the camera */ - private void startPreview() { + private void closeCamera() { + mVideoCallManager.closeCamera(); + } + + /** + * This method starts the camera preview and recording + */ + private void startPreviewAndRecording() { try { mCameraPreview.setVisibility(View.VISIBLE); mVideoCallManager.startCameraPreview(mCameraSurface); + mVideoCallManager.startCameraRecording(); } catch (IOException ioe) { closeCamera(); - loge("Exception while setting preview texture, " + ioe.toString()); + loge("Exception startPreviewAndRecording, " + ioe.toString()); } } /** - * This method disconnect and releases the camera + * This method stops the camera recording and preview */ - private void closeCamera() { - mVideoCallManager.closeCamera(); - } - - /** - * This method stops the camera preview - */ - private void stopPreview() { - mCameraPreview.setVisibility(View.GONE); + private void stopRecordingAndPreview() { + mCameraPreview.setVisibility(View.INVISIBLE); + mVideoCallManager.stopCameraRecording(); mVideoCallManager.stopCameraPreview(); } @@ -314,11 +341,6 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac mCameraSurface = surface; if (isCameraInitNeeded()) { initializeCamera(); - } else { - // Set preview display if the surface is being created and preview - // was already started. That means preview display was set to null - // and we need to set it now. - mVideoCallManager.setDisplay(mCameraSurface); } } else if (surface.equals(mFarEndView.getSurfaceTexture())) { if (DBG) log("Video surface texture created"); @@ -331,7 +353,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { if (surface.equals(mCameraPreview.getSurfaceTexture())) { if (DBG) log("CameraPreview surface texture destroyed"); - stopPreview(); + stopRecordingAndPreview(); closeCamera(); mCameraSurface = null; } else if (surface.equals(mFarEndView.getSurfaceTexture())) { @@ -369,7 +391,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac // Stop the preview and close the camera now because other // activities may need to use it if (mVideoCallManager.getCameraState() != CameraState.CAMERA_CLOSED) { - stopPreview(); + stopRecordingAndPreview(); closeCamera(); } break; @@ -407,22 +429,21 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac * initialized the zoom control */ private void initializeZoom() { - // Get the parameter to make sure we have the up-to-date zoom value. - mParameters = mVideoCallManager.getCameraParameters(); - if(mParameters == null) { + ImsCamera imsCamera = mVideoCallManager.getImsCameraInstance(); + if (imsCamera == null) { return; } - if (!mParameters.isZoomSupported()) { + if (!imsCamera.isZoomSupported()) { mZoomControl.setVisibility(View.GONE); // Disable ZoomControl return; } mZoomControl.setVisibility(View.VISIBLE); // Enable ZoomControl - mZoomMax = mParameters.getMaxZoom(); + mZoomMax = imsCamera.getMaxZoom(); // Currently we use immediate zoom for fast zooming to get better UX and // there is no plan to take advantage of the smooth zoom. mZoomControl.setZoomMax(mZoomMax); - mZoomControl.setZoomIndex(mParameters.getZoom()); + mZoomControl.setZoomIndex(DEFAULT_CAMERA_ZOOM_VALUE); mZoomControl.setOnZoomChangeListener(new ZoomChangeListener()); } @@ -433,11 +454,10 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac */ private void onZoomValueChanged(int index) { mZoomValue = index; - + ImsCamera imsCamera = mVideoCallManager.getImsCameraInstance(); // Set zoom - if (mParameters.isZoomSupported()) { - mParameters.setZoom(mZoomValue); - mVideoCallManager.setCameraParameters(mParameters); + if (imsCamera.isZoomSupported()) { + imsCamera.setZoom(mZoomValue); } } @@ -447,26 +467,22 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac private void initializeCameraParams() { try { // Get the parameter to make sure we have the up-to-date value. - mParameters = mVideoCallManager.getCameraParameters(); + ImsCamera imsCamera = mVideoCallManager.getImsCameraInstance(); // Set the camera preview size if (mIsMediaLoopback) { // In loopback mode the IMS is hard coded to render the // camera frames of only the size 176x144 on the far end surface - mParameters.setPreviewSize(176, 144); + imsCamera.setPreviewSize(LOOPBACK_MODE_WIDTH, LOOPBACK_MODE_HEIGHT); } else { - log("Supported Preview Sizes = " + mParameters.getSupportedPreviewSizes()); log("Set Preview Size directly with negotiated Height = " + mVideoCallManager.getNegotiatedHeight() + " negotiated width= " + mVideoCallManager.getNegotiatedWidth()); - mParameters.setPreviewSize(mVideoCallManager.getNegotiatedWidth(), + imsCamera.setPreviewSize(mVideoCallManager.getNegotiatedWidth(), mVideoCallManager.getNegotiatedHeight()); - setFpsRange(); + imsCamera.setPreviewFpsRange(mVideoCallManager.getNegotiatedFps()); } - - mVideoCallManager.setCameraParameters(mParameters); } catch (RuntimeException e) { - log("Error setting Camera preview size/fps exception=" + e); - log("Supported Preview sizes = " + mParameters.getSupportedPreviewSizes()); + loge("Error setting Camera preview size/fps exception=" + e); } } @@ -494,7 +510,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac // Stop the preview and close the camera now because other // activities may need to use it if (mVideoCallManager.getCameraState() != CameraState.CAMERA_CLOSED) { - stopPreview(); + stopRecordingAndPreview(); closeCamera(); } mCameraPreview.setVisibility(View.GONE); @@ -510,68 +526,17 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } /** - * Select the best possible fps range from the supported list of fps ranges. - * Find the auto mode range that camera supports and use low value of that - * range and high value as negotiated value For eg. if supported values are - * (7.5,30), (20,20), (10,10), (30,30) and negotiated fps is 25 then fps - * should be set to (7.5,25). - */ - void setFpsRange() { - // Camera apis need the FPS values scaled by 1000 - int negotiatedFPS = mVideoCallManager.getNegotiatedFPS() * MEDIA_TO_CAMERA_CONV_UNIT; - List fpsRangeList = mParameters.getSupportedPreviewFpsRange(); - - // Initialize bestFpsRange low and high values to 0 - int bestFpsLow = 0; - int bestFpsHigh = 0; - - for (int i = 0; i < fpsRangeList.size(); i++) { - int currFpsHigh = fpsRangeList.get(i)[1]; - int currFpsLow = fpsRangeList.get(i)[0]; - if (DBG) { - Log.d(LOG_TAG, "Supported FPS range = " + currFpsLow + " : " - + currFpsHigh); - } - - if (currFpsHigh != currFpsLow - && currFpsLow <= negotiatedFPS - && negotiatedFPS <= currFpsHigh) { - bestFpsLow = currFpsLow; - bestFpsHigh = negotiatedFPS; - break; - } - } - - if (!(bestFpsHigh == 0 && bestFpsLow == 0 )) { - if (DBG) { - Log.d(LOG_TAG, "Best FPS range for the negotiated FPS of " + negotiatedFPS + " is " - + bestFpsLow + " : " + bestFpsHigh); - } - mParameters.setPreviewFpsRange(bestFpsLow, bestFpsHigh); - } else { - Log.e(LOG_TAG, "Best FPS range for the negotiated FPS of " + negotiatedFPS - + " is not found"); - } - } - - /** - * This method resizes the camera preview based on the aspect ratio - * supported by camera and the size of VideoCallPanel - * + * This method resizes the camera preview based on the size of the + * VideoCallPanel * @param targetSize */ private void resizeCameraPreview(int targetSize) { - if (DBG) log("resizeCameraPreview"); - + if (DBG) log("resizeCameraPreview targetSize=" + targetSize); // For now, set the preview size to be 1/4th of the VideoCallPanel - mPreviewSize = mVideoCallManager.getCameraPreviewSize(targetSize / 4, true); - if (mPreviewSize != null) { - log("Camera view width:" + mPreviewSize.width + ", height:" + mPreviewSize.height); - ViewGroup.LayoutParams cameraPreivewLp = mCameraPreview.getLayoutParams(); - cameraPreivewLp.height = mPreviewSize.height; - cameraPreivewLp.width = mPreviewSize.width; - mCameraPreview.setLayoutParams(cameraPreivewLp); - } + ViewGroup.LayoutParams cameraPreivewLp = mCameraPreview.getLayoutParams(); + cameraPreivewLp.height = targetSize / 4; + cameraPreivewLp.width = targetSize / 4; + mCameraPreview.setLayoutParams(cameraPreivewLp); } /** @@ -602,7 +567,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac // Stop camera preview if already running if (mVideoCallManager.getCameraState() != CameraState.CAMERA_CLOSED) { - stopPreview(); + stopRecordingAndPreview(); closeCamera(); } @@ -631,6 +596,10 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } } + public void startOrientationListener(boolean start) { + mVideoCallManager.startOrientationListener(start); + } + private void log(String msg) { Log.d(LOG_TAG, msg); } -- cgit v1.2.3 From 6660c44470ec862cccd8501781c82d95740d2b2d Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Sat, 23 Nov 2013 18:07:24 -0800 Subject: IMS-VT: Enable modify call functionality on InCallUI Enable modify call functionality on InCallUI Change-Id: I26930732c5c8638ea5fc880178d0b8983223531e --- src/com/android/incallui/CallCommandClient.java | 6 ++-- src/com/android/incallui/CallHandlerService.java | 16 +++++++++ src/com/android/incallui/CallUtils.java | 15 ++++++--- src/com/android/incallui/InCallActivity.java | 6 ++-- src/com/android/incallui/InCallPresenter.java | 41 ++++++++++++++---------- 5 files changed, 57 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallCommandClient.java b/src/com/android/incallui/CallCommandClient.java index 7505626f..c6b1910d 100644 --- a/src/com/android/incallui/CallCommandClient.java +++ b/src/com/android/incallui/CallCommandClient.java @@ -259,14 +259,14 @@ public class CallCommandClient { } } - public void modifyCallInitiate(int callId) { + public void modifyCallInitiate(int callId, int callType) { if (mCommandService == null) { Log.e(this, "Cannot modifyCall(); CallCommandService == null"); return; } try { - Log.v(this, "modifyCall() "); - mCommandService.modifyCallInitiate(callId); + Log.v(this, "modifyCall(), callId=" + callId + " callType=" + callType); + mCommandService.modifyCallInitiate(callId, callType); } catch (RemoteException e) { Log.e(this, "Error on modifyCall()."); } diff --git a/src/com/android/incallui/CallHandlerService.java b/src/com/android/incallui/CallHandlerService.java index a4825c6d..6f5915b3 100644 --- a/src/com/android/incallui/CallHandlerService.java +++ b/src/com/android/incallui/CallHandlerService.java @@ -247,6 +247,19 @@ public class CallHandlerService extends Service { mAudioModeProvider = null; } + public void doModifyCall(Call call) { + Log.d(TAG, "doModifyCall: Call:" + call); + if (call != null && mInCallPresenter != null && mCallList != null) { + Log.d(TAG, "doModifyCall: Updating CallList:" + mCallList.getCall(call.getCallId())); + mCallList.onUpdate(call); + mInCallPresenter.onModifyCallRequest(call); + } else { + Log.e(TAG, "doModifyCall: isCallValid=" + (call != null)); + Log.e(TAG, "doModifyCall: isInCallPresenterValid=" + (mInCallPresenter != null)); + Log.e(TAG, "doModifyCall: isCallListValid=" + (mCallList != null)); + } + } + /** * Handles messages from the service so that they get executed on the main thread, where they * can interact with UI. @@ -323,6 +336,9 @@ public class CallHandlerService extends Service { doStop(); break; case ON_UNSOL_CALLMODIFY: + Call call = (Call) msg.obj; + Log.i(TAG, "ON_UNSOL_CALLMODIFY: Call=" + call); + doModifyCall(call); break; case ON_ACTIVE_SUB_CHANGE: Log.i(TAG, "ON_ACTIVE_SUB_CHANGE: " + msg.obj); diff --git a/src/com/android/incallui/CallUtils.java b/src/com/android/incallui/CallUtils.java index 0afcfcd3..bcf942b8 100644 --- a/src/com/android/incallui/CallUtils.java +++ b/src/com/android/incallui/CallUtils.java @@ -51,10 +51,17 @@ public class CallUtils { } public static boolean hasCallModifyFailed(Call call) { - final CallDetails cd = getCallModifyDetails(call); - return cd != null && cd.getErrorInfo() != null - && (Integer.parseInt(cd.getErrorInfo()) == 0); - + final CallDetails modifyCallDetails = getCallModifyDetails(call); + boolean hasError = false; + try { + if (modifyCallDetails != null && modifyCallDetails.getErrorInfo() != null) { + hasError = !modifyCallDetails.getErrorInfo().isEmpty() + && Integer.parseInt(modifyCallDetails.getErrorInfo()) != 0; + } + } catch (Exception e) { + hasError = true; + } + return hasError; } private static CallDetails getCallDetails(Call call) { diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index 92343d3e..7d975c13 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -441,13 +441,13 @@ public class InCallActivity extends Activity { public void onClick(DialogInterface dialog, int item) { Toast.makeText(getApplicationContext(), items.get(item), Toast.LENGTH_SHORT).show(); final int selCallType = itemToCallType.get(item); - Log.d("Videocall", - "ModifyCall called: upgrade to " + CallUtils.fromCallType(selCallType)); + log("Videocall: ModifyCall: upgrade/downgrade to " + + CallUtils.fromCallType(selCallType)); InCallPresenter.getInstance().sendModifyCallRequest(callId, selCallType); dialog.dismiss(); } }; - int currCallType = CallUtils.getCallType( CallList.getInstance().getCall(callId)); + int currCallType = CallUtils.getCallType(CallList.getInstance().getCall(callId)); int index = itemToCallType.indexOf(currCallType); builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), index, listener); alert = builder.create(); diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 8ba186fa..10054271 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -30,6 +30,7 @@ import android.content.ActivityNotFoundException; import com.android.services.telephony.common.Call; import com.android.services.telephony.common.Call.Capabilities; +import com.android.services.telephony.common.CallDetails; import com.google.common.collect.Lists; import java.util.ArrayList; @@ -166,12 +167,20 @@ public class InCallPresenter implements CallList.Listener { /** * Sends modify call request to the other party. + * * @param callId id of the call to modify. * @param callType Proposed call type. */ public void sendModifyCallRequest(int callId, int callType) { - log("VideoCall: Sending modify call request. callId=" + callId + " callType=" + callType); - // CallCommandClient.getInstance().modifyCall(callId, callType); + log("VideoCall: Sending modify call request, callId=" + callId + " callType=" + callType); + Call call = CallList.getInstance().getCall(callId); + if (call != null && call.getCallModifyDetails() != null) { + CallDetails cd = call.getCallModifyDetails(); + cd.setCallType(callType); + CallCommandClient.getInstance().modifyCallInitiate(callId, callType); + } else { + loge("VideoCall: Sending modify call request failed: call=" + call); + } } /** @@ -182,26 +191,24 @@ public class InCallPresenter implements CallList.Listener { */ public void modifyCallConfirm(boolean accept, Call call) { log("VideoCall: ModifyCallConfirm: accept=" + accept + " call=" + call); - final int callId = call.getCallId(); - int callType = accept ? CallUtils.getProposedCallType(call) : CallUtils.getCallType(call); - // CallCommandClient.getInstance().modifyCallConfirm(call.getCallId(), callType); + CallCommandClient.getInstance().modifyCallConfirm(accept, call.getCallId()); } /** * Handles modify call request and shows dialog to user for accepting or * rejecting the modify call */ - public void onModifyCallRequest(int callId) { - final Call call = CallList.getInstance().getCall(callId); - if (call != null) { - - final int currCallType = CallUtils.getCallType(call); - final int proposedCallType = CallUtils.getProposedCallType(call); - final boolean error = CallUtils.hasCallModifyFailed(call); + public void onModifyCallRequest(Call call) { + Preconditions.checkNotNull(call); + final int callId = call.getCallId(); + final int currCallType = CallUtils.getCallType(call); + final int proposedCallType = CallUtils.getProposedCallType(call); + final boolean error = CallUtils.hasCallModifyFailed(call); - log("VideoCall onMoifyCallRequest: CallId = " + callId + " currCallType= " - + currCallType - + " proposedCallType= " + proposedCallType + " error=" + error); + log("VideoCall onMoifyCallRequest: CallId =" + callId + " currCallType=" + + currCallType + + " proposedCallType= " + proposedCallType + " error=" + error); + try { if (isUserConsentRequired(proposedCallType, currCallType)) { if (mInCallActivity != null) { mInCallActivity.displayModifyCallConsentDialog(call); @@ -209,8 +216,8 @@ public class InCallPresenter implements CallList.Listener { Log.e(this, "VideoCall: onMoifyCallRequest: InCallActivity is null."); } } - } else { - loge("onModifyCallRequest: Can't find call with callId="+callId); + } catch (ArrayIndexOutOfBoundsException e) { + Log.e(this, "VideoCall: onModifyCallRequest failed. ", e); } } -- cgit v1.2.3 From d6e8ea435025aceadbf58ba78c4a2dd7fc745027 Mon Sep 17 00:00:00 2001 From: Sandeep Gutta Date: Mon, 11 Nov 2013 19:23:13 +0530 Subject: DSDA: Add InCallUI DSDA support. -Add tab view support for DSDA -Add support to display voice calls based on the current active subscription. -Add few utilities in CallList, which required for handling voice calls across multiple subscription. Change-Id: Ib683f7c3b41ed3bb04367be1e9c331908bc46004 --- src/com/android/incallui/CallButtonPresenter.java | 13 +- src/com/android/incallui/CallCardFragment.java | 4 +- src/com/android/incallui/CallHandlerService.java | 2 +- src/com/android/incallui/CallList.java | 120 +++++++- src/com/android/incallui/InCallActivity.java | 33 ++- src/com/android/incallui/InCallPresenter.java | 24 +- .../android/incallui/msim/MSimAnswerFragment.java | 325 +++++++++++++++++++++ .../android/incallui/msim/MSimAnswerPresenter.java | 198 +++++++++++++ .../android/incallui/msim/MSimInCallActivity.java | 232 +++++++++++++++ 9 files changed, 937 insertions(+), 14 deletions(-) create mode 100644 src/com/android/incallui/msim/MSimAnswerFragment.java create mode 100644 src/com/android/incallui/msim/MSimAnswerPresenter.java create mode 100644 src/com/android/incallui/msim/MSimInCallActivity.java (limited to 'src') diff --git a/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java index 1f0ee4d9..20049f76 100644 --- a/src/com/android/incallui/CallButtonPresenter.java +++ b/src/com/android/incallui/CallButtonPresenter.java @@ -35,7 +35,8 @@ import android.telephony.PhoneNumberUtils; * Logic for call buttons. */ public class CallButtonPresenter extends Presenter - implements InCallStateListener, AudioModeListener, IncomingCallListener { + implements InCallStateListener, AudioModeListener, IncomingCallListener, + CallList.ActiveSubChangeListener { private Call mCall; private boolean mAutomaticallyMuted = false; @@ -58,6 +59,7 @@ public class CallButtonPresenter extends Presenter mActiveSubChangeListeners = + Lists.newArrayList(); /** * Static singleton accessor method. @@ -74,6 +79,8 @@ public class CallList { public void onUpdate(Call call) { Log.d(this, "onUpdate - ", call); + updateActiveSuscription(); + updateCallInMap(call); notifyListenersOfChange(); } @@ -101,6 +108,8 @@ public class CallList { public void onIncoming(Call call, List textMessages) { Log.d(this, "onIncoming - " + call); + updateActiveSuscription(); + updateCallInMap(call); updateCallTextMap(call, textMessages); @@ -115,6 +124,8 @@ public class CallList { public void onUpdate(List callsToUpdate) { Log.d(this, "onUpdate(...)"); + updateActiveSuscription(); + Preconditions.checkNotNull(callsToUpdate); for (Call call : callsToUpdate) { Log.d(this, "\t" + call); @@ -283,6 +294,11 @@ public class CallList { * TODO: Improve this logic to sort by call time. */ public Call getCallWithState(int state, int positionToFind) { + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + return getCallWithState(state, positionToFind, getActiveSubscription()); + } + Call retval = null; int position = 0; for (Call call : mCallMap.values()) { @@ -435,6 +451,13 @@ public class CallList { Log.d(this, "EVENT_DISCONNECTED_TIMEOUT ", msg.obj); finishDisconnectedCall((Call) msg.obj); break; + case EVENT_NOTIFY_CHANGE: + Log.d(this, "EVENT_NOTIFY_CHANGE: "); + notifyListenersOfChange(); + for (ActiveSubChangeListener listener : mActiveSubChangeListeners) { + listener.onActiveSubChanged(getActiveSubscription()); + } + break; default: Log.wtf(this, "Message not expected: " + msg.what); break; @@ -480,12 +503,103 @@ public class CallList { * Called when active subscription changes. */ public void onActiveSubChanged(int activeSub) { - Log.d(this, "onActiveSubChanged: old = " + mSubscription + " new = " + activeSub); - - mSubscription = activeSub; + Log.d(this, "onActiveSubChanged = " + activeSub); + if (existsLiveCall(activeSub)) { + setActiveSubscription(activeSub); + } } public int getActiveSubscription() { return mSubscription; } + + /** + * Called to update the latest active subscription id, and also it + * notifies the registred clients about subscription change information. + */ + public void setActiveSubscription(int subscription) { + if (subscription != mSubscription) { + Log.i(this, "setActiveSubscription, old = " + mSubscription + " new = " + subscription); + mSubscription = subscription; + final Message msg = mHandler.obtainMessage(EVENT_NOTIFY_CHANGE, null); + mHandler.sendMessage(msg); + } + } + + /** + * Returns true, if any voice call in ACTIVE on the provided subscription. + */ + public boolean existsLiveCall(int subscription) { + for (Call call : mCallMap.values()) { + if (!isCallDead(call) && (call.getSubscription() == subscription)) { + return true; + } + } + return false; + } + + /** + * This method checks whether any other subscription currently has active voice + * call other than current active subscription, if yes it makes that other + * subscription as active subscription i.e user visible subscription. + */ + public boolean switchToOtherActiveSubscription() { + int activeSub = getActiveSubscription(); + boolean subSwitched = false; + + for (int i = 0; i < MSimTelephonyManager.getDefault().getPhoneCount(); i++) { + if ((i != activeSub) && existsLiveCall(i)) { + Log.i(this, "switchToOtherActiveSubscription, sub = " + i); + subSwitched = true; + setActiveSubscription(i); + break; + } + } + return subSwitched; + } + + /** + * Its a utility, gets the current active subscription from TeleService and + * updates the mSubscription member variable. + */ + public void updateActiveSuscription() { + if (!MSimTelephonyManager.getDefault().isMultiSimEnabled()) { + return; + } + setActiveSubscription(CallCommandClient.getInstance().getActiveSubscription()); + } + + /** + * Returns the [position]th call which belongs to provided subscription and + * found in the call map with the specified state. + */ + public Call getCallWithState(int state, int positionToFind, int subscription) { + Call retval = null; + int position = 0; + for (Call call : mCallMap.values()) { + if ((call.getState() == state) && (call.getSubscription() == subscription)) { + if (position >= positionToFind) { + retval = call; + break; + } else { + position++; + } + } + } + return retval; + } + + public void addActiveSubChangeListener(ActiveSubChangeListener listener) { + Preconditions.checkNotNull(listener); + mActiveSubChangeListeners.add(listener); + } + + public void removeActiveSubChangeListener(ActiveSubChangeListener listener) { + Preconditions.checkNotNull(listener); + mActiveSubChangeListeners.remove(listener); + } + + public interface ActiveSubChangeListener { + public void onActiveSubChanged(int subscription); + } } diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index 92343d3e..80692f32 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -36,6 +36,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; +import android.telephony.MSimTelephonyManager; import android.view.KeyEvent; import android.view.View; import android.view.Window; @@ -52,13 +53,13 @@ public class InCallActivity extends Activity { private static final int INVALID_RES_ID = -1; - private CallButtonFragment mCallButtonFragment; - private CallCardFragment mCallCardFragment; + protected CallButtonFragment mCallButtonFragment; + protected CallCardFragment mCallCardFragment; private AnswerFragment mAnswerFragment; - private DialpadFragment mDialpadFragment; - private ConferenceManagerFragment mConferenceManagerFragment; + protected DialpadFragment mDialpadFragment; + protected ConferenceManagerFragment mConferenceManagerFragment; private boolean mIsForegroundActivity; - private AlertDialog mDialog; + protected AlertDialog mDialog; private AlertDialog mModifyCallPromptDialog; /** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */ @@ -70,6 +71,11 @@ public class InCallActivity extends Activity { super.onCreate(icicle); + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + return; + } + // set this flag so this activity will stay in front of the keyguard // Have the WindowManager filter out touch events that are "too fat". getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED @@ -94,6 +100,11 @@ public class InCallActivity extends Activity { Log.d(this, "onStart()..."); super.onStart(); + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + return; + } + // setting activity should be last thing in setup process InCallPresenter.getInstance().setActivity(this); } @@ -148,7 +159,7 @@ public class InCallActivity extends Activity { return mIsForegroundActivity; } - private boolean hasPendingErrorDialog() { + protected boolean hasPendingErrorDialog() { return mDialog != null; } /** @@ -168,6 +179,11 @@ public class InCallActivity extends Activity { */ @Override public void finish() { + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + super.finish(); + return; + } Log.i(this, "finish(). Dialog showing: " + (mDialog != null)); // skip finish if we are still showing a dialog. @@ -347,7 +363,7 @@ public class InCallActivity extends Activity { } } - private void initializeInCall() { + protected void initializeInCall() { if (mCallButtonFragment == null) { mCallButtonFragment = (CallButtonFragment) getFragmentManager() .findFragmentById(R.id.callButtonFragment); @@ -661,4 +677,7 @@ public class InCallActivity extends Activity { Log.e(this, msg); } + public void updateDsdaTab() { + Log.e(this, "updateDsdaTab : Not supported "); + } } diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 8ba186fa..d17e3e47 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -20,6 +20,8 @@ package com.android.incallui; +import android.telephony.MSimTelephonyManager; + import com.android.incallui.service.PhoneNumberService; import com.google.android.collect.Sets; import com.google.common.base.Preconditions; @@ -159,6 +161,11 @@ public class InCallPresenter implements CallList.Listener { final boolean doFinish = (mInCallActivity != null && isActivityStarted()); Log.i(this, "Hide in call UI: " + doFinish); + if ((mCallList != null) && !(mCallList.existsLiveCall(mCallList.getActiveSubscription())) + && mCallList.switchToOtherActiveSubscription()) { + return; + } + if (doFinish) { mInCallActivity.finish(); } @@ -329,6 +336,11 @@ public class InCallPresenter implements CallList.Listener { Log.d(this, "Notify " + listener + " of state " + mInCallState.toString()); listener.onStateChange(mInCallState, callList); } + + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA && (mInCallActivity != null)) { + mInCallActivity.updateDsdaTab(); + } } /** @@ -353,6 +365,11 @@ public class InCallPresenter implements CallList.Listener { for (IncomingCallListener listener : mIncomingCallListeners) { listener.onIncomingCall(mInCallState, call); } + + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA && (mInCallActivity != null)) { + mInCallActivity.updateDsdaTab(); + } } /** @@ -778,7 +795,12 @@ public class InCallPresenter implements CallList.Listener { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_USER_ACTION); - intent.setClass(mContext, InCallActivity.class); + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + intent.setClass(mContext, MSimInCallActivity.class); + } else { + intent.setClass(mContext, InCallActivity.class); + } if (showDialpad) { intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true); } diff --git a/src/com/android/incallui/msim/MSimAnswerFragment.java b/src/com/android/incallui/msim/MSimAnswerFragment.java new file mode 100644 index 00000000..92ff5c2c --- /dev/null +++ b/src/com/android/incallui/msim/MSimAnswerFragment.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2013 The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * 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 + */ + +package com.android.incallui; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; + +import com.google.common.base.Preconditions; + +import java.util.ArrayList; + +/** + * + */ +public class MSimAnswerFragment extends BaseFragment + implements GlowPadWrapper.AnswerListener, MSimAnswerPresenter.AnswerUi { + + /** + * The popup showing the list of canned responses. + * + * This is an AlertDialog containing a ListView showing the possible choices. This may be null + * if the InCallScreen hasn't ever called showRespondViaSmsPopup() yet, or if the popup was + * visible once but then got dismissed. + */ + private Dialog mCannedResponsePopup = null; + + /** + * The popup showing a text field for users to type in their custom message. + */ + private AlertDialog mCustomMessagePopup = null; + + private ArrayAdapter mTextResponsesAdapter = null; + + private GlowPadWrapper mGlowpad; + + public MSimAnswerFragment() { + } + + @Override + public MSimAnswerPresenter createPresenter() { + return new MSimAnswerPresenter(); + } + + @Override + MSimAnswerPresenter.AnswerUi getUi() { + return this; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mGlowpad = (GlowPadWrapper) inflater.inflate(R.layout.answer_fragment, + container, false); + + Log.d(this, "Creating view for answer fragment ", this); + Log.d(this, "Created from activity", getActivity()); + mGlowpad.setAnswerListener(this); + + return mGlowpad; + } + + @Override + public void onDestroyView() { + Log.d(this, "onDestroyView"); + if (mGlowpad != null) { + mGlowpad.stopPing(); + mGlowpad = null; + } + super.onDestroyView(); + } + + @Override + public void showAnswerUi(boolean show) { + getView().setVisibility(show ? View.VISIBLE : View.GONE); + + Log.d(this, "Show answer UI: " + show); + if (show) { + mGlowpad.startPing(); + } else { + mGlowpad.stopPing(); + } + } + + @Override + public void showTextButton(boolean show) { + final int targetResourceId = show + ? R.array.incoming_call_widget_3way_targets + : R.array.incoming_call_widget_2way_targets; + + if (targetResourceId != mGlowpad.getTargetResourceId()) { + if (show) { + // Answer, Decline, and Respond via SMS. + mGlowpad.setTargetResources(targetResourceId); + mGlowpad.setTargetDescriptionsResourceId( + R.array.incoming_call_widget_3way_target_descriptions); + mGlowpad.setDirectionDescriptionsResourceId( + R.array.incoming_call_widget_3way_direction_descriptions); + } else { + // Answer or Decline. + mGlowpad.setTargetResources(targetResourceId); + mGlowpad.setTargetDescriptionsResourceId( + R.array.incoming_call_widget_2way_target_descriptions); + mGlowpad.setDirectionDescriptionsResourceId( + R.array.incoming_call_widget_2way_direction_descriptions); + } + + mGlowpad.reset(false); + } + } + + @Override + public void showMessageDialog() { + final ListView lv = new ListView(getActivity()); + + Preconditions.checkNotNull(mTextResponsesAdapter); + lv.setAdapter(mTextResponsesAdapter); + lv.setOnItemClickListener(new RespondViaSmsItemClickListener()); + + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()).setCancelable( + true).setView(lv); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + if (mGlowpad != null) { + mGlowpad.startPing(); + } + } + }); + mCannedResponsePopup = builder.create(); + mCannedResponsePopup.show(); + } + + private boolean isCannedResponsePopupShowing() { + if (mCannedResponsePopup != null) { + return mCannedResponsePopup.isShowing(); + } + return false; + } + + private boolean isCustomMessagePopupShowing() { + if (mCustomMessagePopup != null) { + return mCustomMessagePopup.isShowing(); + } + return false; + } + + /** + * Dismiss the canned response list popup. + * + * This is safe to call even if the popup is already dismissed, and even if you never called + * showRespondViaSmsPopup() in the first place. + */ + private void dismissCannedResponsePopup() { + if (mCannedResponsePopup != null) { + mCannedResponsePopup.dismiss(); // safe even if already dismissed + mCannedResponsePopup = null; + } + } + + /** + * Dismiss the custom compose message popup. + */ + private void dismissCustomMessagePopup() { + if (mCustomMessagePopup != null) { + mCustomMessagePopup.dismiss(); + mCustomMessagePopup = null; + } + } + + public void dismissPendingDialogues() { + if (isCannedResponsePopupShowing()) { + dismissCannedResponsePopup(); + } + + if (isCustomMessagePopupShowing()) { + dismissCustomMessagePopup(); + } + } + + public boolean hasPendingDialogs() { + return !(mCannedResponsePopup == null && mCustomMessagePopup == null); + } + + /** + * Shows the custom message entry dialog. + */ + public void showCustomMessageDialog() { + // Create an alert dialog containing an EditText + final EditText et = new EditText(getActivity()); + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()).setCancelable( + true).setView(et) + .setPositiveButton(R.string.custom_message_send, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // The order is arranged in a way that the popup will be destroyed when the + // InCallActivity is about to finish. + final String textMessage = et.getText().toString().trim(); + dismissCustomMessagePopup(); + getPresenter().rejectCallWithMessage(textMessage); + } + }) + .setNegativeButton(R.string.custom_message_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismissCustomMessagePopup(); + getPresenter().onDismissDialog(); + } + }) + .setTitle(R.string.respond_via_sms_custom_message); + mCustomMessagePopup = builder.create(); + + // Enable/disable the send button based on whether there is a message in the EditText + et.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + final Button sendButton = mCustomMessagePopup.getButton( + DialogInterface.BUTTON_POSITIVE); + sendButton.setEnabled(s != null && s.toString().trim().length() != 0); + } + }); + + // Keyboard up, show the dialog + mCustomMessagePopup.getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + mCustomMessagePopup.show(); + + // Send button starts out disabled + final Button sendButton = mCustomMessagePopup.getButton(DialogInterface.BUTTON_POSITIVE); + sendButton.setEnabled(false); + } + + @Override + public void configureMessageDialog(ArrayList textResponses) { + final ArrayList textResponsesForDisplay = new ArrayList(textResponses); + + textResponsesForDisplay.add(getResources().getString( + R.string.respond_via_sms_custom_message)); + mTextResponsesAdapter = new ArrayAdapter(getActivity(), + android.R.layout.simple_list_item_1, android.R.id.text1, textResponsesForDisplay); + } + + @Override + public void onAnswer(int callType) { + getPresenter().onAnswer(callType); + } + + @Override + public void onDecline() { + getPresenter().onDecline(); + } + + @Override + public void onText() { + getPresenter().onText(); + } + + /** + * OnItemClickListener for the "Respond via SMS" popup. + */ + public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener { + + /** + * Handles the user selecting an item from the popup. + */ + @Override + public void onItemClick(AdapterView parent, // The ListView + View view, // The TextView that was clicked + int position, long id) { + Log.d(this, "RespondViaSmsItemClickListener.onItemClick(" + position + ")..."); + final String message = (String) parent.getItemAtPosition(position); + Log.v(this, "- message: '" + message + "'"); + dismissCannedResponsePopup(); + + // The "Custom" choice is a special case. + // (For now, it's guaranteed to be the last item.) + if (position == (parent.getCount() - 1)) { + // Show the custom message dialog + showCustomMessageDialog(); + } else { + getPresenter().rejectCallWithMessage(message); + } + } + } +} diff --git a/src/com/android/incallui/msim/MSimAnswerPresenter.java b/src/com/android/incallui/msim/MSimAnswerPresenter.java new file mode 100644 index 00000000..375b05de --- /dev/null +++ b/src/com/android/incallui/msim/MSimAnswerPresenter.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2013 The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * 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 + */ + +package com.android.incallui; + +import android.telephony.MSimTelephonyManager; +import com.android.services.telephony.common.Call; + +import java.util.ArrayList; + +/** + * Presenter for the Incoming call widget. + */ +public class MSimAnswerPresenter extends Presenter + implements CallList.CallUpdateListener, CallList.Listener, + CallList.ActiveSubChangeListener { + + private static final String TAG = MSimAnswerPresenter.class.getSimpleName(); + + private int mCallId[] = {Call.INVALID_CALL_ID, Call.INVALID_CALL_ID}; + private Call mCall[] = {null, null}; + + @Override + public void onUiReady(AnswerUi ui) { + super.onUiReady(ui); + + final CallList calls = CallList.getInstance(); + final Call call = calls.getIncomingCall(); + // TODO: change so that answer presenter never starts up if it's not incoming. + if (call != null) { + processIncomingCall(call); + } + + // Listen for incoming calls. + calls.addListener(this); + CallList.getInstance().addActiveSubChangeListener(this); + } + + @Override + public void onUiUnready(AnswerUi ui) { + super.onUiUnready(ui); + + int subscription = CallList.getInstance().getActiveSubscription(); + CallList.getInstance().removeListener(this); + + // This is necessary because the activity can be destroyed while an incoming call exists. + // This happens when back button is pressed while incoming call is still being shown. + if (mCallId[subscription] != Call.INVALID_CALL_ID) { + CallList.getInstance().removeCallUpdateListener(mCallId[subscription], this); + } + CallList.getInstance().removeActiveSubChangeListener(this); + } + + @Override + public void onCallListChange(CallList callList) { + // no-op + } + + @Override + public void onDisconnect(Call call) { + // no-op + } + + @Override + public void onIncomingCall(Call call) { + int subscription = call.getSubscription(); + // TODO: Ui is being destroyed when the fragment detaches. Need clean up step to stop + // getting updates here. + Log.d(this, "onIncomingCall: " + this); + if (getUi() != null) { + if (call.getCallId() != mCallId[subscription]) { + // A new call is coming in. + processIncomingCall(call); + } + } + } + + private void processIncomingCall(Call call) { + int subscription = call.getSubscription(); + mCallId[subscription] = call.getCallId(); + mCall[subscription] = call; + + // Listen for call updates for the current call. + CallList.getInstance().addCallUpdateListener(mCallId[subscription], this); + + Log.d(TAG, "Showing incoming for call id: " + mCallId[subscription] + " " + this); + final ArrayList textMsgs = CallList.getInstance().getTextResponses( + call.getCallId()); + getUi().showAnswerUi(true); + + if (call.can(Call.Capabilities.RESPOND_VIA_TEXT) && textMsgs != null) { + getUi().showTextButton(true); + getUi().configureMessageDialog(textMsgs); + } else { + getUi().showTextButton(false); + } + } + + + @Override + public void onCallStateChanged(Call call) { + Log.d(this, "onCallStateChange() " + call + " " + this); + if (call.getState() != Call.State.INCOMING && call.getState() != Call.State.CALL_WAITING) { + int subscription = call.getSubscription(); + // Stop listening for updates. + CallList.getInstance().removeCallUpdateListener(mCallId[subscription], this); + + getUi().showAnswerUi(false); + + // mCallId will hold the state of the call. We don't clear the mCall variable here as + // it may be useful for sending text messages after phone disconnects. + mCallId[subscription] = Call.INVALID_CALL_ID; + } + } + + public void onAnswer(int callType) { + int subscription = CallList.getInstance().getActiveSubscription(); + if (mCallId[subscription] == Call.INVALID_CALL_ID) { + return; + } + + Log.d(this, "onAnswer " + mCallId[subscription]); + + CallCommandClient.getInstance().answerCall(mCallId[subscription]); + } + + public void onDecline() { + int subscription = CallList.getInstance().getActiveSubscription(); + Log.d(this, "onDecline " + mCallId[subscription]); + + CallCommandClient.getInstance().rejectCall(mCall[subscription], false, null); + } + + public void onText() { + if (getUi() != null) { + getUi().showMessageDialog(); + } + } + + public void rejectCallWithMessage(String message) { + int subscription = CallList.getInstance().getActiveSubscription(); + Log.d(this, "sendTextToDefaultActivity()..."); + + CallCommandClient.getInstance().rejectCall(mCall[subscription], true, message); + + onDismissDialog(); + } + + public void onDismissDialog() { + InCallPresenter.getInstance().onDismissDialog(); + } + + interface AnswerUi extends Ui { + public void showAnswerUi(boolean show); + public void showTextButton(boolean show); + public void showMessageDialog(); + public void configureMessageDialog(ArrayList textResponses); + } + + @Override + public void onActiveSubChanged(int subscription) { + final CallList calls = CallList.getInstance(); + final Call call = calls.getIncomingCall(); + + if ((call != null) && (call.getCallId() == mCallId[subscription])) { + Log.i(TAG, "Show incoming for call id: " + mCallId[subscription] + " " + this); + final ArrayList textMsgs = CallList.getInstance().getTextResponses( + call.getCallId()); + getUi().showAnswerUi(true); + + if (call.can(Call.Capabilities.RESPOND_VIA_TEXT) && textMsgs != null) { + getUi().showTextButton(true); + getUi().configureMessageDialog(textMsgs); + } else { + getUi().showTextButton(false); + } + } else if ((call == null) && (calls.existsLiveCall(subscription))) { + Log.i(TAG, "Hide incoming for call id: " + mCallId[subscription] + " " + this); + getUi().showAnswerUi(false); + } + } +} diff --git a/src/com/android/incallui/msim/MSimInCallActivity.java b/src/com/android/incallui/msim/MSimInCallActivity.java new file mode 100644 index 00000000..3f61a595 --- /dev/null +++ b/src/com/android/incallui/msim/MSimInCallActivity.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2013 The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * Copyright (C) 2006 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.app.ActionBar; +import android.app.FragmentTransaction; +import android.app.ActionBar.Tab; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.telephony.MSimTelephonyManager; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * Phone app "multisim in call" screen. + */ +public class MSimInCallActivity extends InCallActivity { + + private MSimAnswerFragment mAnswerFragment; + + private final int TAB_COUNT_ONE = 1; + private final int TAB_COUNT_TWO = 2; + private final int TAB_POSITION_FIRST = 0; + + private Tab[] mDsdaTab = new Tab[TAB_COUNT_TWO]; + private boolean[] mDsdaTabAdd = {false, false}; + + @Override + protected void onCreate(Bundle icicle) { + Log.d(this, "onCreate()... this = " + this); + + super.onCreate(icicle); + + // set this flag so this activity will stay in front of the keyguard + // Have the WindowManager filter out touch events that are "too fat". + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES); + + requestWindowFeature(Window.FEATURE_ACTION_BAR); + + getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + getActionBar().setDisplayShowTitleEnabled(false); + getActionBar().setDisplayShowHomeEnabled(false); + + // Inflate everything in incall_screen.xml and add it to the screen. + setContentView(R.layout.incall_screen_msim); + + initializeInCall(); + + initializeDsdaSwitchTab(); + Log.d(this, "onCreate(): exit"); + } + + @Override + protected void onStart() { + Log.d(this, "onStart()..."); + super.onStart(); + + // setting activity should be last thing in setup process + InCallPresenter.getInstance().setActivity(this); + } + + @Override + public void finish() { + Log.i(this, "finish(). Dialog showing: " + (mDialog != null)); + + // skip finish if we are still showing a dialog. + if (!hasPendingErrorDialog() && !mAnswerFragment.hasPendingDialogs()) { + super.finish(); + } + } + + @Override + protected void initializeInCall() { + if (mCallButtonFragment == null) { + mCallButtonFragment = (CallButtonFragment) getFragmentManager() + .findFragmentById(R.id.callButtonFragment); + mCallButtonFragment.getView().setVisibility(View.INVISIBLE); + } + + if (mCallCardFragment == null) { + mCallCardFragment = (CallCardFragment) getFragmentManager() + .findFragmentById(R.id.callCardFragment); + } + + if (mAnswerFragment == null) { + mAnswerFragment = (MSimAnswerFragment) getFragmentManager() + .findFragmentById(R.id.answerFragment); + } + + if (mDialpadFragment == null) { + mDialpadFragment = (DialpadFragment) getFragmentManager() + .findFragmentById(R.id.dialpadFragment); + mDialpadFragment.getView().setVisibility(View.INVISIBLE); + } + + if (mConferenceManagerFragment == null) { + mConferenceManagerFragment = (ConferenceManagerFragment) getFragmentManager() + .findFragmentById(R.id.conferenceManagerFragment); + mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE); + } + } + + @Override + public void dismissPendingDialogs() { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + mAnswerFragment.dismissPendingDialogues(); + } + + private void initializeDsdaSwitchTab() { + int phoneCount = MSimTelephonyManager.getDefault().getPhoneCount(); + ActionBar bar = getActionBar(); + View[] mDsdaTabLayout = new View[phoneCount]; + TypedArray icons = getResources().obtainTypedArray(R.array.sim_icons); + int[] subString = {R.string.sub_1, R.string.sub_2}; + + for (int i = 0; i < phoneCount; i++) { + mDsdaTabLayout[i] = getLayoutInflater() + .inflate(R.layout.msim_tab_sub_info, null); + + ((ImageView)mDsdaTabLayout[i].findViewById(R.id.tabSubIcon)) + .setBackground(icons.getDrawable(i)); + + ((TextView)mDsdaTabLayout[i].findViewById(R.id.tabSubText)) + .setText(subString[i]); + + mDsdaTab[i] = bar.newTab().setCustomView(mDsdaTabLayout[i]) + .setTabListener(new TabListener(i)); + } + } + + @Override + public void updateDsdaTab() { + int phoneCount = MSimTelephonyManager.getDefault().getPhoneCount(); + ActionBar bar = getActionBar(); + + for (int i = 0; i < phoneCount; i++) { + if (CallList.getInstance().existsLiveCall(i)) { + if (!mDsdaTabAdd[i]) { + addDsdaTab(i); + } + } else { + removeDsdaTab(i); + } + } + + updateDsdaTabSelection(); + } + + private void addDsdaTab(int subscription) { + ActionBar bar = getActionBar(); + int tabCount = bar.getTabCount(); + + if (tabCount < subscription) { + bar.addTab(mDsdaTab[subscription], false); + } else { + bar.addTab(mDsdaTab[subscription], subscription, false); + } + mDsdaTabAdd[subscription] = true; + } + + private void removeDsdaTab(int subscription) { + ActionBar bar = getActionBar(); + int tabCount = bar.getTabCount(); + + for (int i = 0; i < tabCount; i++) { + if (bar.getTabAt(i).equals(mDsdaTab[subscription])) { + bar.removeTab(mDsdaTab[subscription]); + mDsdaTabAdd[subscription] = false; + return; + } + } + } + + private void updateDsdaTabSelection() { + ActionBar bar = getActionBar(); + int barCount = bar.getTabCount(); + + if (barCount == TAB_COUNT_ONE) { + bar.selectTab(bar.getTabAt(TAB_POSITION_FIRST)); + } else if (barCount == TAB_COUNT_TWO) { + bar.selectTab(bar.getTabAt(CallList.getInstance().getActiveSubscription())); + } + } + + private class TabListener implements ActionBar.TabListener { + int mSubscription; + + public TabListener(int subId) { + mSubscription = subId; + } + + public void onTabSelected(Tab tab, FragmentTransaction ft) { + ActionBar bar = getActionBar(); + + if (CallList.getInstance().existsLiveCall(mSubscription)) { + CallCommandClient.getInstance().setActiveSubscription(mSubscription); + } + } + + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + } + + public void onTabReselected(Tab tab, FragmentTransaction ft) { + } + } +} -- cgit v1.2.3 From fa0d370b759e5a8645f12bd448d8204f8d11ea02 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Wed, 27 Nov 2013 16:33:37 -0800 Subject: IMS-VT: Do not change visibility of the camera preview. Decouple the visibility of the camera preview from the API starting/stopping camera preview and recording. Change-Id: I01ad3745f159bf0d032b1ae1fef8aee003313e88 CRs-Fixed: 581912 --- src/com/android/incallui/VideoCallPanel.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/VideoCallPanel.java b/src/com/android/incallui/VideoCallPanel.java index b15a4d46..e01283ec 100644 --- a/src/com/android/incallui/VideoCallPanel.java +++ b/src/com/android/incallui/VideoCallPanel.java @@ -314,7 +314,6 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac */ private void startPreviewAndRecording() { try { - mCameraPreview.setVisibility(View.VISIBLE); mVideoCallManager.startCameraPreview(mCameraSurface); mVideoCallManager.startCameraRecording(); } catch (IOException ioe) { @@ -327,7 +326,6 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac * This method stops the camera recording and preview */ private void stopRecordingAndPreview() { - mCameraPreview.setVisibility(View.INVISIBLE); mVideoCallManager.stopCameraRecording(); mVideoCallManager.stopCameraPreview(); } @@ -571,6 +569,10 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac closeCamera(); } + log("VideoCall: switchCamera: IsCameraNeeded=" + mCameraNeeded + " cameraId=" + cameraId); + final boolean showCameraPreview = mCameraNeeded && cameraId != CAMERA_UNKNOWN; + mCameraPreview.setVisibility(showCameraPreview ? TextureView.VISIBLE : TextureView.GONE); + // Restart camera if camera doesn't need to stay off if (isCameraInitNeeded()) { initializeCamera(); -- cgit v1.2.3 From f7288a172c20695739d948ec41f7cc99fe0f1211 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Mon, 2 Dec 2013 17:15:43 -0800 Subject: IMS-VT: Relayout VideoCallPanel to create surface textures Relayout VideoCallPanel to create surface textures. Change-Id: I915f71a00ac45971e5726f5bce0ac9e836e6fdf6 --- src/com/android/incallui/CallCardFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index 077538a4..652f41eb 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -601,7 +601,7 @@ public class CallCardFragment extends BaseFragment Date: Tue, 3 Dec 2013 22:44:09 -0800 Subject: Enable InCall UI logging -Enable VERBOSE and DEBUG logs by default in InCall UI Change-Id: Iac30ce90775df181f143e89053596209cda9328e --- src/com/android/incallui/Log.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/Log.java b/src/com/android/incallui/Log.java index c859e5c6..8d3e0043 100644 --- a/src/com/android/incallui/Log.java +++ b/src/com/android/incallui/Log.java @@ -24,9 +24,8 @@ public class Log { // Generic tag for all In Call logging private static final String TAG = "InCall"; - public static final boolean DEBUG = android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); - public static final boolean VERBOSE = android.util.Log.isLoggable(TAG, - android.util.Log.VERBOSE); + public static final boolean DEBUG = true; + public static final boolean VERBOSE = true; public static final String TAG_DELIMETER = " - "; public static void d(String tag, String msg) { -- cgit v1.2.3 From 54ba74f060868d8e182212653123599411151810 Mon Sep 17 00:00:00 2001 From: Rekha Kumar Date: Fri, 6 Dec 2013 15:03:57 -0800 Subject: IMS-VT: Surface Texture creation fix -When upgrade from VoLTE to VT is done, camera surface texture and far end surface texture are not created. -Remove forced requestLayout from CallCardFragment's showVideoCallWidgets method Change-Id: Ie73b5ef081681769579beedf27fefbd1b68cc231 CRs-Fixed: 583967 --- src/com/android/incallui/CallCardFragment.java | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index caa725a3..0772fc4c 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -641,7 +641,6 @@ public class CallCardFragment extends BaseFragment Date: Mon, 9 Dec 2013 12:41:19 +0530 Subject: InCallUI not dismissing after end call is clicked. In MT InCallScreen, the customMessage dialog which appears when user tries to reject a call with message, is not set to null when user cancels the dialog. InCallUIActvity is not finished if there is any pending dialog which is not null and hence this issue is seen. Set the dialog variable to null once the dialog is cancelled by the user. Change-Id: I7866862fa406a86a3fb1d11bd71dda8e34623088 CRs-Fixed: 583477 --- src/com/android/incallui/AnswerFragment.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/com/android/incallui/AnswerFragment.java b/src/com/android/incallui/AnswerFragment.java index cc2c9ad3..4f62472a 100644 --- a/src/com/android/incallui/AnswerFragment.java +++ b/src/com/android/incallui/AnswerFragment.java @@ -263,6 +263,13 @@ public class AnswerFragment extends BaseFragment Date: Wed, 11 Dec 2013 14:21:14 +0530 Subject: Ims: Silently drop the call after SRVCC Clearing SRVCC call from the "mcallmap" list maintained by InCallUI. Change-Id: Ia48d35e8b372c78390c36fc0af8fbb0ccfcfac5d CRs-Fixed: 576256 --- src/com/android/incallui/CallList.java | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java index 2a290fc2..c6852b29 100644 --- a/src/com/android/incallui/CallList.java +++ b/src/com/android/incallui/CallList.java @@ -367,16 +367,27 @@ public class CallList { if (call.getState() == Call.State.DISCONNECTED) { // update existing (but do not add!!) disconnected calls if (mCallMap.containsKey(id)) { + final Call.DisconnectCause disconnCause = call.getDisconnectCause(); + Log.d(this, "disconnect cause: " + disconnCause); + if (disconnCause == Call.DisconnectCause.SRVCC_CALL_DROP) { + Log.d(this, "SRVCC call so silently removing call entry"); + //silently remove the call entry + call.setState(Call.State.IDLE); + mCallMap.remove(id); + updated = false; + } else { - // For disconnected calls, we want to keep them alive for a few seconds so that the - // UI has a chance to display anything it needs when a call is disconnected. + // For disconnected calls, we want to keep them alive for a few seconds + // so that the UI has a chance to display anything it needs when a + // call is disconnected. - // Set up a timer to destroy the call after X seconds. - final Message msg = mHandler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call); - mHandler.sendMessageDelayed(msg, getDelayForDisconnect(call)); + // Set up a timer to destroy the call after X seconds. + final Message msg = mHandler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call); + mHandler.sendMessageDelayed(msg, getDelayForDisconnect(call)); - mCallMap.put(id, call); - updated = true; + mCallMap.put(id, call); + updated = true; + } } } else if (!isCallDead(call)) { mCallMap.put(id, call); -- cgit v1.2.3 From 90e069f01958744ecb820acfdfb45fde04b622a5 Mon Sep 17 00:00:00 2001 From: Rekha Kumar Date: Wed, 16 Oct 2013 16:34:54 -0700 Subject: IMS-VT: Peer Resolution feature and split screen fix -Implement Peer resolution feature. Change far end size based on peer resolution -When VideoCallPanel's size changes, its children need to update their size accordingly. This enables the farend view ie.video_view to resize correctly after background call is dropped, in multiple call scenarios Change-Id: I4313b972042d124b0ad2155d2cef6f1e8d418b7f CRs-Fixed: 555075, 588216 --- src/com/android/incallui/MediaHandler.java | 67 +++++++++++++++++++--- src/com/android/incallui/VideoCallManager.java | 24 +++++++- src/com/android/incallui/VideoCallPanel.java | 77 +++++++++++++++++++------- 3 files changed, 140 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/MediaHandler.java b/src/com/android/incallui/MediaHandler.java index c04d565f..bdf60638 100644 --- a/src/com/android/incallui/MediaHandler.java +++ b/src/com/android/incallui/MediaHandler.java @@ -42,6 +42,9 @@ import android.util.Log; */ public class MediaHandler extends Handler { + //Use QVGA as default resolution + private static final int DEFAULT_WIDTH = 240; + private static final int DEFAULT_HEIGHT = 320; public static final int DPL_INIT_SUCCESSFUL = 0; public static final int DPL_INIT_FAILURE = -1; public static final int DPL_INIT_MULTIPLE = -2; @@ -61,11 +64,17 @@ public class MediaHandler extends Handler { private static native int nativeGetNegotiatedHeight(); private static native int nativeGetNegotiatedWidth(); private static native int nativeGetUIOrientationMode(); + private static native int nativeGetPeerHeight(); + private static native int nativeGetPeerWidth(); private static native void nativeRegisterForMediaEvents(MediaHandler instance); + public static final int MEDIA_EVENT = 0; + + //Following values are from the IMS VT API documentation public static final int PARAM_READY_EVT = 1; public static final int START_READY_EVT = 2; public static final int DISPLAY_MODE_EVT = 5; + public static final int PEER_RESOLUTION_CHANGE_EVT = 6; protected final RegistrantList mDisplayModeEventRegistrants = new RegistrantList(); @@ -84,7 +93,9 @@ public class MediaHandler extends Handler { private static int mUIOrientationMode = PORTRAIT_MODE; private static short mNegotiatedFps = 20; - private MediaEventListener mMediaEventListener; + private int mPeerHeight = DEFAULT_HEIGHT; + private int mPeerWidth = DEFAULT_WIDTH; + private IMediaEventListener mMediaEventListener; public RegistrantList mCvoModeOnRegistrant = new RegistrantList(); // Use a singleton @@ -106,10 +117,11 @@ public class MediaHandler extends Handler { private MediaHandler() { } - public interface MediaEventListener { + public interface IMediaEventListener { void onParamReadyEvent(); void onDisplayModeEvent(); void onStartReadyEvent(); + void onPeerResolutionChangeEvent(); } static { @@ -222,6 +234,22 @@ public class MediaHandler extends Handler { return mNegotiatedFps; } + /** + * Get Peer Height + */ + public int getPeerHeight() { + Log.d(TAG, "Peer Height = " + mPeerHeight); + return mPeerHeight; + } + + /** + * Get Peer Width + */ + public int getPeerWidth() { + Log.d(TAG, "Peer Width = " + mPeerWidth); + return mPeerWidth; + } + /** * Register for event that will invoke * {@link MediaHandler#onMediaEvent(int)} @@ -231,15 +259,11 @@ public class MediaHandler extends Handler { nativeRegisterForMediaEvents(instance); } - public void setMediaEventListener(MediaEventListener listener) { + public void setMediaEventListener(IMediaEventListener listener) { mMediaEventListener = listener; } - /** - * Callback method that is invoked when Media events occur - */ - public void onMediaEvent(int eventId) { - Log.d(TAG, "onMediaEvent eventId = " + eventId); + private void doOnMediaEvent(int eventId) { switch (eventId) { case PARAM_READY_EVT: Log.d(TAG, "Received PARAM_READY_EVT. Updating negotiated values"); @@ -250,6 +274,15 @@ public class MediaHandler extends Handler { mMediaEventListener.onParamReadyEvent(); } break; + case PEER_RESOLUTION_CHANGE_EVT: + mPeerHeight = nativeGetPeerHeight(); + mPeerWidth = nativeGetPeerWidth(); + Log.d(TAG, "Received PEER_RESOLUTION_CHANGE_EVENT. Updating peer values" + + " mPeerHeight=" + mPeerHeight + " mPeerWidth=" + mPeerWidth); + if (mMediaEventListener != null) { + mMediaEventListener.onPeerResolutionChangeEvent(); + } + break; case START_READY_EVT: Log.d(TAG, "Received START_READY_EVT. Camera frames can be sent now"); if (mMediaEventListener != null) { @@ -266,7 +299,25 @@ public class MediaHandler extends Handler { default: Log.e(TAG, "Received unknown event id=" + eventId); } + } + + /** + * Callback method that is invoked when Media events occur + */ + public void onMediaEvent(int eventId) { + Log.d(TAG, "onMediaEvent eventId = " + eventId); + final Message msg = obtainMessage(MEDIA_EVENT, eventId, 0); + sendMessage(msg); + } + public void handleMessage(Message msg) { + switch (msg.what) { + case MEDIA_EVENT: + doOnMediaEvent(msg.arg1); + break; + default: + Log.e(TAG, "Received unknown msg id = " + msg.what); + } } private void processUIOrientationMode() { diff --git a/src/com/android/incallui/VideoCallManager.java b/src/com/android/incallui/VideoCallManager.java index 8a8c08eb..f9bd0461 100644 --- a/src/com/android/incallui/VideoCallManager.java +++ b/src/com/android/incallui/VideoCallManager.java @@ -36,6 +36,7 @@ import android.os.Message; import android.util.Log; import com.android.incallui.CameraHandler.CameraState; import com.android.incallui.CvoHandler.CvoEventListener; +import com.android.incallui.MediaHandler.IMediaEventListener; import java.io.IOException; @@ -47,6 +48,7 @@ import java.io.IOException; public class VideoCallManager { private static final String TAG = "VideoCallManager"; private static VideoCallManager mInstance; // Use a singleton + private static final int INVALID_SIZE = -1; private CameraHandler mCameraHandler; private MediaHandler mMediaHandler; private CvoHandler mCvoHandler; @@ -54,6 +56,8 @@ public class VideoCallManager { @Override public void handleMessage(Message msg) { AsyncResult ar; + log("handleMessage id=" + msg.what); + switch (msg.what) { case CVO_MODE_REQUEST_CHANGED: ar = (AsyncResult) msg.obj; @@ -298,7 +302,7 @@ public class VideoCallManager { mCameraHandler.stopCameraRecording(); } - public void setOnParamReadyListener(VideoCallPanel.ParamReadyListener listener) { + public void setMediaEventListener(VideoCallPanel.MediaEventListener listener) { mMediaHandler.setMediaEventListener(listener); } @@ -318,7 +322,25 @@ public class VideoCallManager { } } + public float getPeerAspectRatio() { + int peerHeight = mMediaHandler.getPeerHeight(); + int peerWidth = mMediaHandler.getPeerWidth(); + //Check for invalid size and divide by zero + if (peerHeight == INVALID_SIZE || peerWidth == INVALID_SIZE || peerHeight == 0) { + loge("getPeerAspectRatio ERROR peerHeight=" + peerHeight + " peerWidth=" + peerWidth); + return INVALID_SIZE; + } + float aspectRatio = (float) peerWidth / peerHeight; + log("aspectRatio= " + aspectRatio + " peerHeight=" + peerHeight + " peerWidth=" + + peerWidth); + return aspectRatio; + } + private void log(String msg) { Log.d(TAG, msg); } + + private void loge(String msg) { + Log.e(TAG, msg); + } } diff --git a/src/com/android/incallui/VideoCallPanel.java b/src/com/android/incallui/VideoCallPanel.java index e01283ec..1f71c0f3 100644 --- a/src/com/android/incallui/VideoCallPanel.java +++ b/src/com/android/incallui/VideoCallPanel.java @@ -57,6 +57,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac private static final int MEDIA_TO_CAMERA_CONV_UNIT = 1000; private static final int DEFAULT_CAMERA_ZOOM_VALUE = 0; + private static final int INVALID_SIZE = -1; private Context mContext; private VideoCallManager mVideoCallManager; @@ -69,6 +70,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac private SurfaceTexture mCameraSurface; private SurfaceTexture mFarEndSurface; private ImageView mCameraPicker; + private final Resize mResize = new Resize(); private int mZoomMax; private int mZoomValue; // The current zoom value @@ -79,6 +81,9 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac private int mBackCameraId; private int mCameraId; + private int mHeight = INVALID_SIZE; + private int mWidth = INVALID_SIZE; + // Property used to indicate that the Media running in loopback mode private boolean mIsMediaLoopback = false; @@ -96,10 +101,11 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } } + /** * This class implements the listener for PARAM READY EVENT */ - public class ParamReadyListener implements MediaHandler.MediaEventListener { + public class MediaEventListener implements MediaHandler.IMediaEventListener { @Override public void onParamReadyEvent() { CameraState cameraState = mVideoCallManager.getCameraState(); @@ -130,6 +136,14 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac // NO-OP } + @Override + public void onPeerResolutionChangeEvent() { + if (DBG) log("onPeerResolutionChangeEvent"); + + if (mHeight != INVALID_SIZE && mWidth != INVALID_SIZE) { + resizeFarEndView(); + } + } } public class CvoListener implements CvoHandler.CvoEventListener { @@ -200,7 +214,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } // Set media event listener - mVideoCallManager.setOnParamReadyListener(new ParamReadyListener()); + mVideoCallManager.setMediaEventListener(new MediaEventListener()); mVideoCallManager.setCvoEventListener(new CvoListener()); } @@ -237,12 +251,27 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac */ @Override protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) { - if (DBG) log("onSizeChanged"); - if (DBG) log("Video Panel width:" + xNew + ", height:" + yNew); + log("onSizeChanged"); + log("Video Panel xNew=" + xNew + ", yNew=" + yNew + " xOld=" + xOld + " yOld=" + yOld); + if (xNew != xOld || yNew != yOld) { + post(mResize); + } + } - // Resize preview window if the size of the view changed - resizeCameraPreview(yNew); - resizeFarEndView(xNew, yNew); + private class Resize implements Runnable { + @Override + public void run() { + doSizeChanged(); + } + } + + private void doSizeChanged() { + mWidth = getWidth(); + mHeight = getHeight(); + + if (DBG) log("doSizeChanged: VideoCallPanel width=" + mWidth + ", height=" + mHeight); + resizeCameraPreview(); + resizeFarEndView(); } /** @@ -526,32 +555,42 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac /** * This method resizes the camera preview based on the size of the * VideoCallPanel - * @param targetSize */ - private void resizeCameraPreview(int targetSize) { - if (DBG) log("resizeCameraPreview targetSize=" + targetSize); + private void resizeCameraPreview() { + if (DBG) log("resizeCameraPreview: mHeight=" + mHeight); // For now, set the preview size to be 1/4th of the VideoCallPanel ViewGroup.LayoutParams cameraPreivewLp = mCameraPreview.getLayoutParams(); - cameraPreivewLp.height = targetSize / 4; - cameraPreivewLp.width = targetSize / 4; + cameraPreivewLp.height = mHeight / 4; + cameraPreivewLp.width = mHeight / 4; // use mHeight to create small + // square box for camera preview mCameraPreview.setLayoutParams(cameraPreivewLp); } /** * This method resizes the far end view based on the size of VideoCallPanel * Presently supports only full size far end video - * @param targetWidth - * @param targetHeight */ - private void resizeFarEndView(int targetWidth, int targetHeight) { + private void resizeFarEndView() { + int minDimension = Math.min(mWidth, mHeight); + int farEndWidth = mWidth; + int farEndHeight = mHeight; + float aspectRatio = mVideoCallManager.getPeerAspectRatio(); + if (aspectRatio > 1) { + // Width > Height, so fix the width + farEndWidth = minDimension; + farEndHeight = Math.round(minDimension / aspectRatio); + } else if (aspectRatio > 0 && aspectRatio <= 1) { + farEndHeight = minDimension; + farEndWidth = Math.round(aspectRatio * minDimension); + } // In other cases continue with target height and width if (DBG) { - log("resizeFarEndView"); - log("Videocall pandel width:" + targetWidth + ", height:" + targetHeight); + log("resizeFarEndView FarEnd to width:" + farEndWidth + ", height:" + farEndHeight); } ViewGroup.LayoutParams farEndViewLp = mFarEndView.getLayoutParams(); - farEndViewLp.height = targetHeight; - farEndViewLp.width = targetWidth; + farEndViewLp.height = farEndHeight; + farEndViewLp.width = farEndWidth; + mFarEndView.setLayoutParams(farEndViewLp); } -- cgit v1.2.3 From 7c60f711b76061a3fa8751270418c92932950323 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Tue, 10 Dec 2013 18:18:22 -0800 Subject: MS-VT: Fix camera reconfiguration -Reconfigure camera after PARAM_READY only if negotiated values are different from existing camera settings Change-Id: Ieea1cc8113eb52f8c3a5624b6d35081c12c18bbe CRs-Fixed: 542241 --- src/com/android/incallui/MediaHandler.java | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/MediaHandler.java b/src/com/android/incallui/MediaHandler.java index bdf60638..95acd7b9 100644 --- a/src/com/android/incallui/MediaHandler.java +++ b/src/com/android/incallui/MediaHandler.java @@ -209,7 +209,7 @@ public class MediaHandler extends Handler { /** * Get Negotiated Height */ - public static int getNegotiatedHeight() { + public synchronized static int getNegotiatedHeight() { Log.d(TAG, "Negotiated Height = " + mNegotiatedHeight); return mNegotiatedHeight; } @@ -217,7 +217,7 @@ public class MediaHandler extends Handler { /** * Get Negotiated Width */ - public static int getNegotiatedWidth() { + public synchronized static int getNegotiatedWidth() { Log.d(TAG, "Negotiated Width = " + mNegotiatedWidth); return mNegotiatedWidth; } @@ -230,7 +230,7 @@ public class MediaHandler extends Handler { return mUIOrientationMode; } - public static short getNegotiatedFps() { + public synchronized static short getNegotiatedFps() { return mNegotiatedFps; } @@ -267,10 +267,7 @@ public class MediaHandler extends Handler { switch (eventId) { case PARAM_READY_EVT: Log.d(TAG, "Received PARAM_READY_EVT. Updating negotiated values"); - mNegotiatedHeight = nativeGetNegotiatedHeight(); - mNegotiatedWidth = nativeGetNegotiatedWidth(); - mNegotiatedFps = nativeGetNegotiatedFPS(); - if (mMediaEventListener != null) { + if (updatePreviewParams() && mMediaEventListener != null) { mMediaEventListener.onParamReadyEvent(); } break; @@ -320,6 +317,21 @@ public class MediaHandler extends Handler { } } + private synchronized boolean updatePreviewParams() { + int h = nativeGetNegotiatedHeight(); + int w = nativeGetNegotiatedWidth(); + short fps = nativeGetNegotiatedFPS(); + if (mNegotiatedHeight != h + || mNegotiatedWidth != w + || mNegotiatedFps != fps) { + mNegotiatedHeight = h; + mNegotiatedWidth = w; + mNegotiatedFps = fps; + return true; + } + return false; + } + private void processUIOrientationMode() { mCvoModeOnRegistrant.notifyRegistrants(new AsyncResult(null, isCvoModeEnabled(), null)); -- cgit v1.2.3 From 15aa4cf4141c4b91b9e00aedd34206dc66f7d733 Mon Sep 17 00:00:00 2001 From: Ravindra Date: Fri, 27 Dec 2013 18:02:27 +0530 Subject: Update CallCard when Multiparty state change The Callcard gets updated only when there is a change in the call id. Update InCall Callcard UI if there is change in the multiparty state of existing calls. Change-Id: I9748138862c5bb0654dccb7bd231dcc72d61e639 CRs-Fixed: 593087 --- src/com/android/incallui/CallCardPresenter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java index 7c35cc4d..1cdf4c2a 100644 --- a/src/com/android/incallui/CallCardPresenter.java +++ b/src/com/android/incallui/CallCardPresenter.java @@ -245,7 +245,8 @@ public class CallCardPresenter extends Presenter } // otherwise compare call Ids - return call1.getCallId() == call2.getCallId(); + return (call1.getCallId() == call2.getCallId()) && + call1.getCallDetails().isMpty() == call2.getCallDetails().isMpty(); } private void maybeStartSearch(Call call, boolean isPrimary) { -- cgit v1.2.3 From c840a7ba4e0eda19d78adbdf54835254e5243ee9 Mon Sep 17 00:00:00 2001 From: Muhammed Siju Date: Wed, 1 Jan 2014 18:31:46 +0530 Subject: MSIM(DSDA): Ensure phone screen wake up on DSDA incoming call. In case of an incoming call on one sub with a live call on another sub, force InCallActivity to finish() if the screen is off. This is required since the screen will wake up only when InCallActivity is created. Also cancel the on going InCall notification and notify the new notification so that the full screen InCall intent is launched. Change-Id: I778075f503b27c85cb8dd0e58b35df3d3b0f8810 CRs-Fixed: 583161 --- src/com/android/incallui/CallList.java | 16 ++++++++++++++++ src/com/android/incallui/InCallPresenter.java | 9 ++++++++- src/com/android/incallui/StatusBarNotifier.java | 10 ++++++++-- 3 files changed, 32 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java index c6852b29..9b942ed1 100644 --- a/src/com/android/incallui/CallList.java +++ b/src/com/android/incallui/CallList.java @@ -573,6 +573,22 @@ public class CallList { return subSwitched; } + /** + * Method to check if there is any live call in a sub other than the one supplied. + * @param currentSub The subscription to exclude while checking for active calls. + */ + public boolean isAnyOtherSubActive(int currentSub) { + boolean result = false; + for (int i = 0; i < MSimTelephonyManager.getDefault().getPhoneCount(); i++) { + if ((i != currentSub) && existsLiveCall(i)) { + Log.d(this, "Live call found on another sub = " + i); + result = true; + break; + } + } + return result; + } + /** * Its a utility, gets the current active subscription from TeleService and * updates the mSubscription member variable. diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 834570d1..8ad32fd3 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -754,7 +754,14 @@ public class InCallPresenter implements CallList.Listener { // for the call waiting case, we finish() the current activity and start a new one. // There should be no jank from this since the screen is already off and will remain so // until our new activity is up. - if (mProximitySensor.isScreenReallyOff() && isCallWaiting) { + + // In addition to call waiting scenario, we need to force finish() in case of DSDA when + // we get an incoming call on one sub and there is a live call in other sub and screen + // is off. + boolean anyOtherSubActive = (incomingCall != null && mCallList.isAnyOtherSubActive( + mCallList.getActiveSubscription())); + + if (mProximitySensor.isScreenReallyOff() && (isCallWaiting || anyOtherSubActive)) { if (isActivityStarted()) { mInCallActivity.finish(); } diff --git a/src/com/android/incallui/StatusBarNotifier.java b/src/com/android/incallui/StatusBarNotifier.java index 2de1b2d0..98442671 100644 --- a/src/com/android/incallui/StatusBarNotifier.java +++ b/src/com/android/incallui/StatusBarNotifier.java @@ -539,12 +539,18 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { // If a call is onhold during an incoming call, the call actually comes in as // INCOMING. For that case *and* traditional call-waiting, we want to // cancel the notification. + + // For DSDA, we want to cancel the notification if we get an incoming call on + // one sub and there is a live call on another sub. + CallList callList = CallList.getInstance(); boolean isCallWaiting = (call.getState() == Call.State.CALL_WAITING || (call.getState() == Call.State.INCOMING && - CallList.getInstance().getBackgroundCall() != null)); + (callList.getBackgroundCall() != null || + callList.isAnyOtherSubActive(callList.getActiveSubscription())))); if (isCallWaiting) { - Log.i(this, "updateInCallNotification: call-waiting! force relaunch..."); + Log.i(this, "updateInCallNotification: call-waiting or dsda incoming call!" + + " force relaunch..."); // Cancel the IN_CALL_NOTIFICATION immediately before // (re)posting it; this seems to force the // NotificationManager to launch the fullScreenIntent. -- cgit v1.2.3 From 1dd4db1355b2386195b029c4a8d8e558bf7a3aa7 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Tue, 14 Jan 2014 09:27:40 -0800 Subject: IMS-VT: Don't hide video call widgets in AVP retry mode. Telephony hides video call widgets when the call is in AVP retry mode (call type is VT_NO_DIR). This impacts early media feature, since camera and far end preview won't be visible. Don't hide video call widgets in AVP retry mode. Change-Id: I79884439a6ac277d3eb4f65b4554711bc99e756d CRs-Fixed: 597832 --- src/com/android/incallui/CallUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallUtils.java b/src/com/android/incallui/CallUtils.java index bcf942b8..f952ee18 100644 --- a/src/com/android/incallui/CallUtils.java +++ b/src/com/android/incallui/CallUtils.java @@ -37,7 +37,8 @@ public class CallUtils { public static boolean isVideoCall(int callType) { return callType == CallDetails.CALL_TYPE_VT || callType == CallDetails.CALL_TYPE_VT_TX || - callType == CallDetails.CALL_TYPE_VT_RX; + callType == CallDetails.CALL_TYPE_VT_RX || + callType == CallDetails.CALL_TYPE_VT_NODIR; } public static int getCallType(Call call) { @@ -98,8 +99,7 @@ public class CallUtils { if (call == null) return false; Preconditions.checkNotNull(call.getCallDetails()); final int callType = call.getCallDetails().getCallType(); - final boolean isImsVideoCall = isVideoCall(call) || - (callType == CallDetails.CALL_TYPE_VT_NODIR); + final boolean isImsVideoCall = isVideoCall(call); final boolean isImsVoiceCall = (callType == CallDetails.CALL_TYPE_VOICE && call.getCallDetails().getCallDomain() == CallDetails.CALL_DOMAIN_PS); return isImsVideoCall || isImsVoiceCall; -- cgit v1.2.3 From 4bd2eee8100bd62aca691fe2768a6e409b49bae1 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Tue, 21 Jan 2014 14:05:37 -0800 Subject: IMS-VT: VideoPause: Provide a callback function. Provide callback function for OEMs to listen to PLAYER_START_EVENT and PLAYER_STOP_EVENT events. Change-Id: I701e8179cab5a9582152b35b670c2d98cb9cbf7f CRs-Fixed: 555084 --- src/com/android/incallui/MediaHandler.java | 16 ++++++++++++++++ src/com/android/incallui/VideoCallPanel.java | 11 +++++++++++ 2 files changed, 27 insertions(+) (limited to 'src') diff --git a/src/com/android/incallui/MediaHandler.java b/src/com/android/incallui/MediaHandler.java index 95acd7b9..dbf35562 100644 --- a/src/com/android/incallui/MediaHandler.java +++ b/src/com/android/incallui/MediaHandler.java @@ -49,6 +49,9 @@ public class MediaHandler extends Handler { public static final int DPL_INIT_FAILURE = -1; public static final int DPL_INIT_MULTIPLE = -2; + public static final int PLAYER_STATE_STARTED = 0; + public static final int PLAYER_STATE_STOPPED = 1; + private static final String TAG = "VideoCall_MediaHandler"; private static SurfaceTexture mSurface; @@ -73,6 +76,8 @@ public class MediaHandler extends Handler { //Following values are from the IMS VT API documentation public static final int PARAM_READY_EVT = 1; public static final int START_READY_EVT = 2; + public static final int PLAYER_START_EVENT = 3; + public static final int PLAYER_STOP_EVENT = 4; public static final int DISPLAY_MODE_EVT = 5; public static final int PEER_RESOLUTION_CHANGE_EVT = 6; @@ -122,6 +127,7 @@ public class MediaHandler extends Handler { void onDisplayModeEvent(); void onStartReadyEvent(); void onPeerResolutionChangeEvent(); + void onPlayerStateChanged(int state); } static { @@ -293,6 +299,16 @@ public class MediaHandler extends Handler { mMediaEventListener.onDisplayModeEvent(); } break; + case PLAYER_START_EVENT: + if (mMediaEventListener != null) { + mMediaEventListener.onPlayerStateChanged(PLAYER_STATE_STARTED); + } + break; + case PLAYER_STOP_EVENT: + if (mMediaEventListener != null) { + mMediaEventListener.onPlayerStateChanged(PLAYER_STATE_STOPPED); + } + break; default: Log.e(TAG, "Received unknown event id=" + eventId); } diff --git a/src/com/android/incallui/VideoCallPanel.java b/src/com/android/incallui/VideoCallPanel.java index 1f71c0f3..81b2d97e 100644 --- a/src/com/android/incallui/VideoCallPanel.java +++ b/src/com/android/incallui/VideoCallPanel.java @@ -126,6 +126,17 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } } + @Override + public void onPlayerStateChanged(int state) { + if (state == MediaHandler.PLAYER_STATE_STARTED) { + log("PLAYER_STARTED"); + } else if (state == MediaHandler.PLAYER_STATE_STOPPED) { + log("PLAYER_STOPPED"); + } else { + log("UNKOWN_STATE"); + } + } + @Override public void onDisplayModeEvent() { // NO-OP -- cgit v1.2.3 From 8ac17d846c30078db3ad8514c7761ead1b76ca9d Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Fri, 3 Jan 2014 13:45:56 -0800 Subject: IMS-VT: Pass package name to lib-imscamera.so library. IMS Camera interface requires its clients to explicitly pass package name to open the camera. Pass package name to lib-imscamera.so library when open the camera. Change-Id: I55143d310012b961ac1616403abb7df5f2f4b9ab CRs-Fixed: 588960 --- src/com/android/incallui/CameraHandler.java | 6 +++++- src/com/android/incallui/ImsCamera.java | 26 +++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CameraHandler.java b/src/com/android/incallui/CameraHandler.java index 7c0a063e..b8414c1e 100644 --- a/src/com/android/incallui/CameraHandler.java +++ b/src/com/android/incallui/CameraHandler.java @@ -58,6 +58,7 @@ public class CameraHandler { private CameraInfo[] mInfo; private CameraState mCameraState = CameraState.CAMERA_CLOSED; private Context mContext; + private String mPackageName; // Use a singleton. private static CameraHandler mInstance; @@ -94,7 +95,10 @@ public class CameraHandler { private CameraHandler(Context context) { mContext = context; mNumberOfCameras = android.hardware.Camera.getNumberOfCameras(); + mPackageName = context.getPackageName(); + log("Number of cameras supported is: " + mNumberOfCameras); + log("Package name: " + mPackageName); mInfo = new CameraInfo[mNumberOfCameras]; for (int i = 0; i < mNumberOfCameras; i++) { mInfo[i] = new CameraInfo(); @@ -151,7 +155,7 @@ public class CameraHandler { if (mCameraDevice == null) { try { if (DBG) log("opening camera " + cameraId); - mCameraDevice = ImsCamera.open(cameraId); + mCameraDevice = ImsCamera.open(cameraId, mPackageName); mCameraId = cameraId; } catch (Exception e) { loge("fail to connect Camera" + e); diff --git a/src/com/android/incallui/ImsCamera.java b/src/com/android/incallui/ImsCamera.java index de8d9ee3..603f6734 100644 --- a/src/com/android/incallui/ImsCamera.java +++ b/src/com/android/incallui/ImsCamera.java @@ -47,8 +47,11 @@ public class ImsCamera { System.loadLibrary("imscamera_jni"); } + // @deprecated Use overloaded variant and explicitly pass the package name. public static native short native_open(int cameraId); + public static native short native_open(int cameraId, String packageName); + public native short native_release(); public native short native_startPreview(); @@ -73,15 +76,28 @@ public class ImsCamera { public native short native_setPreviewFpsRange(short fps); + // @deprecated Use overloaded variant and explicitly pass the package name. public static ImsCamera open(int cameraId) throws Exception { Log.d(TAG, "open cameraId=" + cameraId); - short error = native_open(cameraId); - if (error != IMS_CAMERA_OPERATION_SUCCESS) { - Log.e(TAG, "open cameraId=" + cameraId + " failed with error=" + error); - throw new Exception(); - } else { + return openImpl(cameraId, null); + } + + public static ImsCamera open(int cameraId, String packageName) throws Exception { + Log.d(TAG, "open cameraId=" + cameraId); + if (packageName == null) throw new IllegalArgumentException(); + return openImpl(cameraId, packageName); + } + + private static ImsCamera openImpl(int cameraId, String packageName) throws Exception { + final short error = + packageName == null ? native_open(cameraId) : native_open(cameraId, packageName); + if (error == IMS_CAMERA_OPERATION_SUCCESS) { return new ImsCamera(); } + + Log.e(TAG, "open cameraId=" + cameraId + " packageName=" + packageName + + " failed with error=" + error); + throw new Exception("Failed to open ImsCamera"); } public short release() { -- cgit v1.2.3 From 2f3c817ccc440144d4b1f8f1ed3af214194c1c66 Mon Sep 17 00:00:00 2001 From: Shriram Ganesh Date: Tue, 28 Jan 2014 13:05:26 -0800 Subject: Fix display of duplicate calls during call hold SRVCC 1. Check for call identification with primary call for SRVCC before updating the secondary call for UI display. Change-Id: If5ea840724c2da94a39778e9db8e9e885cf18f5e CRs-Fixed: 595741 --- src/com/android/incallui/CallCardPresenter.java | 37 +++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java index 1cdf4c2a..40bb269b 100644 --- a/src/com/android/incallui/CallCardPresenter.java +++ b/src/com/android/incallui/CallCardPresenter.java @@ -39,6 +39,7 @@ import com.android.incallui.InCallPresenter.IncomingCallListener; import com.android.services.telephony.common.AudioMode; import com.android.services.telephony.common.Call; import com.android.services.telephony.common.Call.Capabilities; +import com.android.services.telephony.common.CallDetails; import com.android.services.telephony.common.CallIdentification; import com.google.common.base.Preconditions; import com.android.incallui.CallUtils; @@ -157,8 +158,22 @@ public class CallCardPresenter extends Presenter final boolean primaryChanged = !areCallsSame(mPrimary, primary); final boolean secondaryChanged = !areCallsSame(mSecondary, secondary); - mSecondary = secondary; - mPrimary = primary; + + if (primary != null && secondary != null && + primary.getCallDetails().getCallDomain() != secondary.getCallDetails() + .getCallDomain() && areCallsSameOnDifferentDomains(primary, secondary)) { + Log.d(this, "SRVCC scenario primary and secondary are same call Primary " + + mPrimary + " Secondary " + mSecondary); + mSecondary = null; + if (primary.getCallDetails().getCallDomain() == CallDetails.CALL_DOMAIN_PS) { + mPrimary = secondary; //primary overwritten with CS + } else { + mPrimary = primary; //primary retains CS + } + } else { + mSecondary = secondary; + mPrimary = primary; + } if (primaryChanged && mPrimary != null) { // primary call has changed @@ -249,6 +264,24 @@ public class CallCardPresenter extends Presenter call1.getCallDetails().isMpty() == call2.getCallDetails().isMpty(); } + private boolean areCallsSameOnDifferentDomains(Call call1, Call call2) { + if (call1 == null || call2 == null) { + return false; + } + boolean callsSame = false; + if (call1.getCallDetails().isMpty() && call2.getCallDetails().isMpty()) { //MPTY SRVCC + //Currently more than one conference call each on a different domain + //If we hit here it means SRVCC scenario for a conference call + callsSame = true; + Log.d (this, "areCallsSameOnDifferentDomains for Mpty SRVCC call"); + } else if (call1.getNumber() != null && call1.getNumber().equals(call2.getNumber())) { + //Normal SRVCC + callsSame = true; + Log.d (this, "areCallsSameOnDifferentDomains for SRVCC call"); + } + return callsSame; + } + private void maybeStartSearch(Call call, boolean isPrimary) { // no need to start search for conference calls which show generic info. if (call != null && !call.isConferenceCall()) { -- cgit v1.2.3 From 8679ea4a8bdde9a956d0bfcf0f08064f4c868c02 Mon Sep 17 00:00:00 2001 From: Ravindra Date: Tue, 21 Jan 2014 16:03:07 +0530 Subject: Voice Privacy Icon Support for CDMA Voice privacy icons are added to InCallUI app. When VP is enabled corresponding icon is displayed on the UI. Change-Id: Iadf3710f74987eac785f7816abc684d397a691b8 CRs-Fixed: 599389 --- src/com/android/incallui/StatusBarNotifier.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/StatusBarNotifier.java b/src/com/android/incallui/StatusBarNotifier.java index 98442671..19595b82 100644 --- a/src/com/android/incallui/StatusBarNotifier.java +++ b/src/com/android/incallui/StatusBarNotifier.java @@ -451,10 +451,22 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener { // different calls. So if both lines are in use, display info // from the foreground call. And if there's a ringing call, // display that regardless of the state of the other calls. + int resId; + int voicePrivacy = call.getCapabilities() & Call.Capabilities.VOICE_PRIVACY; if (call.getState() == Call.State.ONHOLD) { - return R.drawable.stat_sys_phone_call_on_hold; + if (voicePrivacy != 0) { + resId = R.drawable.stat_sys_vp_phone_call_on_hold; + } else { + resId = R.drawable.stat_sys_phone_call_on_hold; + } + } else { + if (voicePrivacy != 0) { + resId = R.drawable.stat_sys_vp_phone_call; + } else { + resId = R.drawable.stat_sys_phone_call; + } } - return R.drawable.stat_sys_phone_call; + return resId; } /** -- cgit v1.2.3 From 15fb234af81f64b257d52e9700bbab018acb3cc7 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Wed, 5 Feb 2014 16:56:01 -0800 Subject: IMS-VT: Consider null call as non VT call. Consider null call as non VT call. Change-Id: Iebc119daaf757de4d1454ff3b09b07ab0a99a4cf CRs-Fixed: 609702 --- src/com/android/incallui/CallUtils.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallUtils.java b/src/com/android/incallui/CallUtils.java index f952ee18..9c3516ab 100644 --- a/src/com/android/incallui/CallUtils.java +++ b/src/com/android/incallui/CallUtils.java @@ -74,9 +74,7 @@ public class CallUtils { } public static boolean isVideoCall(Call call) { - Preconditions.checkNotNull(call); - Preconditions.checkNotNull(call.getCallDetails()); - return isVideoCall(call.getCallDetails().getCallType()); + return call != null && isVideoCall(call.getCallDetails().getCallType()); } public static String fromCallType(int callType) { -- cgit v1.2.3 From 94a85446d641e61ac67c195e57c21cf22748cc78 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Wed, 4 Dec 2013 16:15:48 -0800 Subject: IMS-VT: Prevent surface texture destruction. Prevent the camera and the far end view destruction. Change-Id: Id38f0422dcd98c13589f6a8d05c642c7cb47cbea CRs-Fixed: 598710 --- src/com/android/incallui/CallCardFragment.java | 10 +++++ src/com/android/incallui/VideoCallPanel.java | 55 +++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index 0772fc4c..97d02cdb 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -148,6 +148,16 @@ public class CallCardFragment extends BaseFragment Date: Tue, 11 Feb 2014 15:47:01 +0530 Subject: Display Conference call Failure to the user When CallModeler receives SUPP_SERVICE_FAILED event after RIL reports failure for any of the supplementary service,User will be informed with a Error Dialog Message. Currently,there is no indication presented to user when a supplementary service operations fails. Change-Id: If3bda2348468c9fb9230a5cc569a7015556a446e CRs-Fixed: 610064 --- src/com/android/incallui/CallHandlerService.java | 12 ++++- src/com/android/incallui/InCallActivity.java | 57 ++++++++++++++++++++++++ src/com/android/incallui/InCallPresenter.java | 6 +++ 3 files changed, 74 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/incallui/CallHandlerService.java b/src/com/android/incallui/CallHandlerService.java index 7a206317..f404b490 100644 --- a/src/com/android/incallui/CallHandlerService.java +++ b/src/com/android/incallui/CallHandlerService.java @@ -50,8 +50,9 @@ public class CallHandlerService extends Service { private static final int ON_DESTROY = 10; private static final int ON_ACTIVE_SUB_CHANGE = 11; private static final int ON_UNSOL_CALLMODIFY = 12; + private static final int ON_SUPP_SERVICE_FAIL = 13; - private static final int LARGEST_MSG_ID = ON_ACTIVE_SUB_CHANGE; + private static final int LARGEST_MSG_ID = ON_SUPP_SERVICE_FAIL; private CallList mCallList; @@ -204,6 +205,11 @@ public class CallHandlerService extends Service { mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_ACTIVE_SUB_CHANGE, activeSub)); } + @Override + public void onSuppServiceFailed(int service) { + mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_SUPP_SERVICE_FAIL, service)); + } + }; private void doStart(ICallCommandService service) { @@ -344,6 +350,10 @@ public class CallHandlerService extends Service { Log.i(TAG, "ON_ACTIVE_SUB_CHANGE: " + msg.obj); mCallList.onActiveSubChanged((Integer) msg.obj); break; + case ON_SUPP_SERVICE_FAIL: + Log.i(TAG, "ON_SUPP_SERVICE_FAIL: "); + mInCallPresenter.onSuppServiceFailed((Integer) msg.obj); + break; default: break; diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index f1a7133a..54fe92e8 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -23,6 +23,7 @@ package com.android.incallui; import java.lang.reflect.Array; import java.util.ArrayList; +import com.android.internal.telephony.Phone; import com.android.services.telephony.common.Call; import com.android.services.telephony.common.Call.State; import com.android.services.telephony.common.CallDetails; @@ -610,6 +611,62 @@ public class InCallActivity extends Activity { } } + /** + * Handle a failure notification for a supplementary service + * (i.e. conference, switch, separate, transfer, etc.). + */ + void onSuppServiceFailed(int service) { + Log.d(this, "onSuppServiceFailed: " + service); + Phone.SuppService result = Phone.SuppService.values()[service]; + int errorMessageResId; + + switch (result) { + case SWITCH: + // Attempt to switch foreground and background/incoming calls failed + // ("Failed to switch calls") + errorMessageResId = R.string.incall_error_supp_service_switch; + break; + + case SEPARATE: + // Attempt to separate a call from a conference call + // failed ("Failed to separate out call") + errorMessageResId = R.string.incall_error_supp_service_separate; + break; + + case TRANSFER: + // Attempt to connect foreground and background calls to + // each other (and hanging up user's line) failed ("Call + // transfer failed") + errorMessageResId = R.string.incall_error_supp_service_transfer; + break; + + case CONFERENCE: + // Attempt to add a call to conference call failed + // ("Conference call failed") + errorMessageResId = R.string.incall_error_supp_service_conference; + break; + + case REJECT: + // Attempt to reject an incoming call failed + // ("Call rejection failed") + errorMessageResId = R.string.incall_error_supp_service_reject; + break; + + case HANGUP: + // Attempt to release a call failed ("Failed to release call(s)") + errorMessageResId = R.string.incall_error_supp_service_hangup; + break; + + case UNKNOWN: + default: + // Attempt to use a service we don't recognize or support + // ("Unsupported service" or "Selected service failed") + errorMessageResId = R.string.incall_error_supp_service_unknown; + break; + } + showErrorDialog(errorMessageResId); + } + /** * Utility function to bring up a generic "error" dialog. */ diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 8ad32fd3..8f24a81d 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -860,6 +860,12 @@ public class InCallPresenter implements CallList.Listener { } } + public void onSuppServiceFailed(int service) { + if (mInCallActivity != null) { + mInCallActivity.onSuppServiceFailed(service); + } + } + /** * Private constructor. Must use getInstance() to get this singleton. */ -- cgit v1.2.3 From 085e467ee6c192b446ddc54860648cd83fc03a9e Mon Sep 17 00:00:00 2001 From: Pavan Kumar Tatavarthi Date: Mon, 3 Feb 2014 18:25:02 -0800 Subject: Add support to Call deflection api 1. Add deflect call api to CallCommandClient Change-Id: I85c91487b9f2385e5ab0377d0db0b81ab4448d26 CRs-Fixed: 609140 --- src/com/android/incallui/CallCommandClient.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src') diff --git a/src/com/android/incallui/CallCommandClient.java b/src/com/android/incallui/CallCommandClient.java index c6b1910d..c6d9b9aa 100644 --- a/src/com/android/incallui/CallCommandClient.java +++ b/src/com/android/incallui/CallCommandClient.java @@ -259,6 +259,19 @@ public class CallCommandClient { } } + public void deflectCall(int callId, String number) { + if (mCommandService == null) { + Log.e(this, "Cannot deflectCall(); CallCommandService == null"); + return; + } + try{ + Log.v(this, "deflectCall() "); + mCommandService.deflectCall(callId, number); + } catch (RemoteException e) { + Log.e(this, "Error on deflectCall().", e); + } + } + public void modifyCallInitiate(int callId, int callType) { if (mCommandService == null) { Log.e(this, "Cannot modifyCall(); CallCommandService == null"); -- cgit v1.2.3 From 5bbc60d489937af554e7b0ff56ff37b6fd06804b Mon Sep 17 00:00:00 2001 From: Pavan Kumar Tatavarthi Date: Wed, 5 Feb 2014 13:08:49 -0800 Subject: Deflect call test using answer call button 1. Provide hooks to deflect call through answer button in the UI for testing deflect call scenarios. 2. Define system property to set the number to which call should be delfected. 3. Define property to deflect call as "persist.radio.deflect.number" Change-Id: Iee3c845bdcd5e5a7bb488a05b89758fa0fa6a6ba CRs-Fixed: 609140 --- src/com/android/incallui/CallCommandClient.java | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/CallCommandClient.java b/src/com/android/incallui/CallCommandClient.java index c6b1910d..92cf6d67 100644 --- a/src/com/android/incallui/CallCommandClient.java +++ b/src/com/android/incallui/CallCommandClient.java @@ -17,6 +17,7 @@ package com.android.incallui; import android.os.RemoteException; +import android.os.SystemProperties; import com.android.internal.telephony.MSimConstants; @@ -252,8 +253,29 @@ public class CallCommandClient { return; } try { - Log.v(this, "acceptCall() " ); - mCommandService.answerCallWithCallType(callId,callType); + /* + * To test call deflection this property has to be set with the + * number to which the call should be deflected. If this property is + * set to a number, on pressing the UI answer button, call deflect + * request will be sent. This is done to provide hooks to test call + * deflection through the UI answer button. For commercialization UI + * should be customized to call this API through the Call deflect UI + * button By default this property is not set and Answer button will + * work as expected + * Example: + * To deflect call to number 12345 + * adb shell setprop persist.radio.deflect.number 12345 + * + * Toggle above property and to invoke answerCallWithCallType + * adb shell setprop persist.radio.deflect.number "" + */ + String deflectcall = SystemProperties.get("persist.radio.deflect.number"); + if (deflectcall != null && !deflectcall.isEmpty()) { + mCommandService.deflectCall(callId, deflectcall); + } else { + Log.v(this, "acceptCall() "); + mCommandService.answerCallWithCallType(callId, callType); + } } catch (RemoteException e) { Log.e(this, "Error on acceptCall().", e); } -- cgit v1.2.3 From 53dc2d381e20ffd73f95224481145ce78c1e80fe Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Tue, 25 Feb 2014 15:05:15 -0800 Subject: IMS-VT: Enable video multitasking Enable video multitasking Change-Id: I7bbb565058a961151bf5628185e7172ee487a574 CRs-Fixed: 608479 --- src/com/android/incallui/CallCardFragment.java | 1 - src/com/android/incallui/CallUtils.java | 20 +- src/com/android/incallui/InCallPresenter.java | 9 +- src/com/android/incallui/VideoCallManager.java | 11 +- src/com/android/incallui/VideoPauseController.java | 244 +++++++++++++++++++++ 5 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 src/com/android/incallui/VideoPauseController.java (limited to 'src') diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index 97d02cdb..afae4cd4 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -606,7 +606,6 @@ public class CallCardFragment extends BaseFragment Date: Thu, 13 Mar 2014 12:13:11 +0530 Subject: Remove dependency on internal telephony Remove dependency on internal telephony to maintain consistency with the AOSP design. Change-Id: Icf582b7c7e73046cb14414ea06babe59262c10ef CRs-Fixed: 628043 --- src/com/android/incallui/InCallActivity.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index 54fe92e8..621865df 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -23,7 +23,6 @@ package com.android.incallui; import java.lang.reflect.Array; import java.util.ArrayList; -import com.android.internal.telephony.Phone; import com.android.services.telephony.common.Call; import com.android.services.telephony.common.Call.State; import com.android.services.telephony.common.CallDetails; @@ -66,6 +65,11 @@ public class InCallActivity extends Activity { /** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */ private boolean mShowDialpadRequested; + // This enum maps to Phone.SuppService defined in telephony + private enum SuppService { + UNKNOWN, SWITCH, SEPARATE, TRANSFER, CONFERENCE, REJECT, HANGUP; + } + @Override protected void onCreate(Bundle icicle) { Log.d(this, "onCreate()... this = " + this); @@ -617,7 +621,7 @@ public class InCallActivity extends Activity { */ void onSuppServiceFailed(int service) { Log.d(this, "onSuppServiceFailed: " + service); - Phone.SuppService result = Phone.SuppService.values()[service]; + SuppService result = SuppService.values()[service]; int errorMessageResId; switch (result) { -- cgit v1.2.3 From 5127011efc1676d7421bb07705dd762fc8aa0169 Mon Sep 17 00:00:00 2001 From: mengsun Date: Tue, 11 Mar 2014 16:51:21 +0800 Subject: Fix "Not able to answer call in the other sub when one sub has call active" The active sub is changed to a incorrect sub on phone state changed event which is earlier than incoming call event, so set the incoming call sub as active sub again when incoming call event got. CRs-Fixed: 625303 Change-Id: Ibcbfc59f7d7f059d8fc04e0fd4f0c385152f9307 --- src/com/android/incallui/CallList.java | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java index 9b942ed1..6a3c8521 100644 --- a/src/com/android/incallui/CallList.java +++ b/src/com/android/incallui/CallList.java @@ -109,6 +109,10 @@ public class CallList { public void onIncoming(Call call, List textMessages) { Log.d(this, "onIncoming - " + call); + // ensure the ringing call is active subscription, since phone state + // changed is notified before new incoming call ringing, and the event + // will switch active sub to a wrong sub(which is not ringing) + CallCommandClient.getInstance().setActiveSubscription(call.getSubscription()); updateActiveSuscription(); updateCallInMap(call); -- cgit v1.2.3 From e2d958d0d9221279d2287583acf15c073a476db2 Mon Sep 17 00:00:00 2001 From: Garik Badalyan Date: Sat, 22 Mar 2014 09:24:36 -0700 Subject: IMS-VT: Don't cache SurfaceTexture of camera preview Don't cache SurfaceTexture of camera preview. Change-Id: If3f1a74fcf1b33887625ea5fe6142e4c4222ccf4 CRs-Fixed: 634036 (cherry picked from commit 3b484d9985204313b971baafd381619f8168b507) --- src/com/android/incallui/VideoCallPanel.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/com/android/incallui/VideoCallPanel.java b/src/com/android/incallui/VideoCallPanel.java index d085b76a..f0b8816c 100644 --- a/src/com/android/incallui/VideoCallPanel.java +++ b/src/com/android/incallui/VideoCallPanel.java @@ -386,14 +386,7 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac if (surface.equals(mCameraPreview.getSurfaceTexture())) { if (DBG) log("Camera surface texture created"); - // Use cached surface texture. - if (mCameraSurface==null) { - log("Caching camera surface texture."); - mCameraSurface = surface; - } else { - log("Resetting camera surface texture"); - mCameraPreview.setSurfaceTexture(mCameraSurface); - } + mCameraSurface = surface; // Initialize Camera as needed. if (isCameraInitNeeded()) { @@ -421,7 +414,8 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac if (DBG) log("CameraPreview surface texture destroyed"); stopRecordingAndPreview(); closeCamera(); - return false; + mCameraSurface = null; + return true; } else if (surface.equals(mFarEndView.getSurfaceTexture())) { if (DBG) log("FarEndView surface texture destroyed"); return false; @@ -683,9 +677,6 @@ public class VideoCallPanel extends RelativeLayout implements TextureView.Surfac } private void releaseCachedSurfaces() { - release(mCameraSurface); - mCameraSurface = null; - release(mFarEndSurface); mFarEndSurface = null; mVideoCallManager.setFarEndSurface(mFarEndSurface); -- cgit v1.2.3 From 3494bb54d625287c459d7763b060b4dc20f07a5a Mon Sep 17 00:00:00 2001 From: Sandeep Gutta Date: Thu, 17 Apr 2014 04:10:03 +0530 Subject: MSIM(DSDA): Do not dismiss answer call UI when call active Do not dismiss answer call UI if device has INCOMING call on any subscription. Change-Id: Ib3fa43779663ae77705f7ee2329166d1bff3e2b5 CRs-Fixed: 649470 --- src/com/android/incallui/msim/MSimAnswerPresenter.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/incallui/msim/MSimAnswerPresenter.java b/src/com/android/incallui/msim/MSimAnswerPresenter.java index 375b05de..f813e65a 100644 --- a/src/com/android/incallui/msim/MSimAnswerPresenter.java +++ b/src/com/android/incallui/msim/MSimAnswerPresenter.java @@ -121,7 +121,12 @@ public class MSimAnswerPresenter extends Presenter // Stop listening for updates. CallList.getInstance().removeCallUpdateListener(mCallId[subscription], this); - getUi().showAnswerUi(false); + final Call incall = CallList.getInstance().getIncomingCall(); + if (incall != null) { + getUi().showAnswerUi(true); + } else { + getUi().showAnswerUi(false); + } // mCallId will hold the state of the call. We don't clear the mCall variable here as // it may be useful for sending text messages after phone disconnects. -- cgit v1.2.3