summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/incallui/AnswerFragment.java37
-rw-r--r--src/com/android/incallui/AnswerPresenter.java17
-rw-r--r--src/com/android/incallui/CallButtonFragment.java61
-rw-r--r--src/com/android/incallui/CallButtonPresenter.java69
-rw-r--r--src/com/android/incallui/CallCardFragment.java245
-rw-r--r--src/com/android/incallui/CallCardPresenter.java48
-rw-r--r--src/com/android/incallui/CallCommandClient.java95
-rw-r--r--src/com/android/incallui/CallHandlerService.java45
-rw-r--r--src/com/android/incallui/CallList.java169
-rw-r--r--src/com/android/incallui/CallUtils.java119
-rw-r--r--src/com/android/incallui/CameraHandler.java350
-rw-r--r--src/com/android/incallui/ConferenceManagerFragment.java11
-rw-r--r--src/com/android/incallui/ConferenceManagerPresenter.java60
-rw-r--r--src/com/android/incallui/ContactInfoCache.java68
-rw-r--r--src/com/android/incallui/CvoHandler.java237
-rw-r--r--src/com/android/incallui/GlowPadWrapper.java34
-rw-r--r--src/com/android/incallui/ImsCamera.java180
-rw-r--r--src/com/android/incallui/InCallActivity.java210
-rw-r--r--src/com/android/incallui/InCallApp.java2
-rw-r--r--src/com/android/incallui/InCallPresenter.java190
-rw-r--r--src/com/android/incallui/Log.java5
-rw-r--r--src/com/android/incallui/MSimAnswerFragment.java325
-rw-r--r--src/com/android/incallui/MSimAnswerPresenter.java198
-rw-r--r--src/com/android/incallui/MSimInCallActivity.java232
-rw-r--r--src/com/android/incallui/MediaHandler.java361
-rw-r--r--src/com/android/incallui/ProximityListener.java80
-rw-r--r--src/com/android/incallui/ProximitySensor.java8
-rw-r--r--src/com/android/incallui/StatusBarNotifier.java10
-rw-r--r--src/com/android/incallui/VideoCallManager.java346
-rw-r--r--src/com/android/incallui/VideoCallPanel.java651
-rw-r--r--src/com/android/incallui/ZoomControl.java124
-rw-r--r--src/com/android/incallui/ZoomControlBar.java152
32 files changed, 4666 insertions, 73 deletions
diff --git a/src/com/android/incallui/AnswerFragment.java b/src/com/android/incallui/AnswerFragment.java
index c76c5071..1a5e8b6e 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");
@@ -136,6 +140,28 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
}
@Override
+ public void showVideoButtons() {
+ Log.d(this, "ims video ");
+ final int targetResourceId = R.array.incoming_call_widget_6way_ims_targets;
+
+ if (targetResourceId != mGlowpad.getTargetResourceId()) {
+ // Answer, Decline, Respond via SMS, and Video options
+ // (VT,VoLTE,VT-TX,VT-RX)
+ mGlowpad.setTargetResources(R.array.incoming_call_widget_6way_ims_targets);
+ mGlowpad.setTargetDescriptionsResourceId(
+ R.array.incoming_call_widget_6way_ims_target_descriptions);
+ mGlowpad.setDirectionDescriptionsResourceId(
+ R.array.incoming_call_widget_6way_ims_direction_descriptions);
+
+ mGlowpad.reset(false);
+ }
+ }
+
+ public boolean isMessageDialogueShowing() {
+ return mCannedResponsePopup != null && mCannedResponsePopup.isShowing();
+ }
+
+ @Override
public void showMessageDialog() {
final ListView lv = new ListView(getActivity());
@@ -238,6 +264,13 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
getPresenter().onDismissDialog();
}
})
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ dismissCustomMessagePopup();
+ getPresenter().onDismissDialog();
+ }
+ })
.setTitle(R.string.respond_via_sms_custom_message);
mCustomMessagePopup = builder.create();
@@ -281,8 +314,8 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresente
}
@Override
- public void onAnswer() {
- getPresenter().onAnswer();
+ public void onAnswer(int callType) {
+ getPresenter().onAnswer(callType);
}
@Override
diff --git a/src/com/android/incallui/AnswerPresenter.java b/src/com/android/incallui/AnswerPresenter.java
index dd4deeb6..a90685f4 100644
--- a/src/com/android/incallui/AnswerPresenter.java
+++ b/src/com/android/incallui/AnswerPresenter.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");
@@ -94,7 +98,11 @@ public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
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<AnswerPresenter.AnswerUi>
}
}
- 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<AnswerPresenter.AnswerUi>
interface AnswerUi extends Ui {
public void showAnswerUi(boolean show);
+ public void showVideoButtons();
public void showTextButton(boolean show);
public void showMessageDialog();
public void configureMessageDialog(ArrayList<String> textResponses);
diff --git a/src/com/android/incallui/CallButtonFragment.java b/src/com/android/incallui/CallButtonFragment.java
index ed769033..a5f3c837 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");
@@ -31,6 +35,7 @@ import android.widget.PopupMenu.OnDismissListener;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.ToggleButton;
+import com.android.internal.telephony.util.BlacklistUtils;
import com.android.services.telephony.common.AudioMode;
/**
@@ -48,6 +53,9 @@ public class CallButtonFragment
private ImageButton mMergeButton;
private ImageButton mAddCallButton;
private ImageButton mSwapButton;
+ private ImageButton mBlacklistButton;
+ private ImageButton mAddParticipantButton;
+ private ImageButton mModifyCallButton;
private PopupMenu mAudioModePopup;
private boolean mAudioModePopupVisible;
@@ -141,6 +149,20 @@ 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);
+
+ // "Add to black list" button
+ mBlacklistButton = (ImageButton) parent.findViewById(R.id.addBlacklistButton);
+ if (BlacklistUtils.isBlacklistEnabled(getActivity())) {
+ mBlacklistButton.setVisibility(View.VISIBLE);
+ mBlacklistButton.setOnClickListener(this);
+ } else {
+ mBlacklistButton.setVisibility(View.GONE);
+ }
+
+ mModifyCallButton = (ImageButton) parent.findViewById(R.id.modifyCallButton);
+ mModifyCallButton.setOnClickListener(this);
return parent;
}
@@ -183,6 +205,15 @@ public class CallButtonFragment
case R.id.dialpadButton:
getPresenter().showDialpadClicked(mShowDialpadButton.isChecked());
break;
+ case R.id.addBlacklistButton:
+ getPresenter().blacklistClicked(getActivity());
+ break;
+ case R.id.addParticipant:
+ getPresenter().addParticipantClicked();
+ break;
+ case R.id.modifyCallButton:
+ getPresenter().modifyCallButtonClicked();
+ break;
default:
Log.wtf(this, "onClick: unexpected");
break;
@@ -190,11 +221,8 @@ public class CallButtonFragment
}
@Override
- public void setEnabled(boolean isEnabled) {
- View view = getView();
- if (view.getVisibility() != View.VISIBLE) {
- view.setVisibility(View.VISIBLE);
- }
+ public void setEnabled(boolean isEnabled, boolean isVisible) {
+ getView().setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
// The main end-call button spanning across the screen.
mEndCallButton.setEnabled(isEnabled);
@@ -207,6 +235,8 @@ public class CallButtonFragment
mMergeButton.setEnabled(isEnabled);
mAddCallButton.setEnabled(isEnabled);
mSwapButton.setEnabled(isEnabled);
+ mBlacklistButton.setEnabled(isEnabled);
+ mAddParticipantButton.setEnabled(isEnabled);
}
@Override
@@ -254,6 +284,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());
@@ -298,6 +332,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 be257378..2bd72bab 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");
@@ -22,15 +26,20 @@ 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.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
import android.telephony.PhoneNumberUtils;
/**
* Logic for call buttons.
*/
public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
- implements InCallStateListener, AudioModeListener, IncomingCallListener {
+ implements InCallStateListener, AudioModeListener, IncomingCallListener,
+ CallList.ActiveSubChangeListener {
private Call mCall;
private boolean mAutomaticallyMuted = false;
@@ -53,6 +62,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
// register for call state changes last
InCallPresenter.getInstance().addListener(this);
InCallPresenter.getInstance().addIncomingCallListener(this);
+ CallList.getInstance().addActiveSubChangeListener(this);
}
@Override
@@ -62,6 +72,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
InCallPresenter.getInstance().removeListener(this);
AudioModeProvider.getInstance().removeListener(this);
InCallPresenter.getInstance().removeIncomingCallListener(this);
+ CallList.getInstance().removeActiveSubChangeListener(this);
}
@Override
@@ -190,6 +201,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
CallCommandClient.getInstance().merge();
}
+ public void addParticipantClicked() {
+ InCallPresenter.getInstance().sendAddParticipantIntent();
+ }
+
public void addCallClicked() {
// Automatically mute the current call
mAutomaticallyMuted = true;
@@ -204,12 +219,41 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
CallCommandClient.getInstance().swap();
}
+ public void blacklistClicked(final Context context) {
+ if (mCall == null) {
+ return;
+ }
+
+ final String number = mCall.getNumber();
+ final String message = context.getString(R.string.blacklist_dialog_message, number);
+
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.blacklist_dialog_title)
+ .setMessage(message)
+ .setPositiveButton(R.string.alert_dialog_yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(this, "hanging up due to blacklist: " + mCall.getCallId());
+ CallCommandClient.getInstance().blacklistAndHangup(mCall.getCallId());
+ }
+ })
+ .setNegativeButton(R.string.alert_dialog_no, null)
+ .show();
+ }
+
public void showDialpadClicked(boolean checked) {
Log.v(this, "Show dialpad " + String.valueOf(checked));
getUi().displayDialpad(checked);
updateExtraButtonRow();
}
+ public void modifyCallButtonClicked() {
+ Call call = CallList.getInstance().getActiveCall();
+ if (call != null) {
+ getUi().displayModifyCallOptions(call.getCallId());
+ }
+ }
+
private void updateUi(InCallState state, Call call) {
final CallButtonUi ui = getUi();
if (ui == null) {
@@ -219,7 +263,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
final boolean isEnabled = state.isConnectingOrConnected() &&
!state.isIncoming() && call != null;
- ui.setEnabled(isEnabled);
+ ui.setEnabled(isEnabled, !state.isIncoming());
Log.d(this, "Updating call UI for call: ", call);
@@ -230,10 +274,12 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
Log.v(this, "Show swap ", call.can(Capabilities.SWAP_CALLS));
Log.v(this, "Show add call ", call.can(Capabilities.ADD_CALL));
Log.v(this, "Show mute ", call.can(Capabilities.MUTE));
+ Log.v(this, "Show modify call ", call.can(Capabilities.MODIFY_CALL));
final boolean canMerge = call.can(Capabilities.MERGE_CALLS);
final boolean canAdd = call.can(Capabilities.ADD_CALL);
final boolean isGenericConference = call.can(Capabilities.GENERIC_CONFERENCE);
+ final boolean canModifyCall = call.can(Capabilities.MODIFY_CALL);
final boolean showMerge = !isGenericConference && canMerge;
@@ -281,8 +327,13 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
}
}
+ ui.enableAddParticipant(call.can(Capabilities.ADD_PARTICIPANT));
+
ui.enableMute(call.can(Capabilities.MUTE));
+ ui.enableModifyCall(canModifyCall);
+ ui.showModifyCall(canModifyCall);
+
// Finally, update the "extra button row": It's displayed above the
// "End" button, but only if necessary. Also, it's never displayed
// while the dialpad is visible (since it would overlap.)
@@ -330,7 +381,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
}
public interface CallButtonUi extends Ui {
- void setEnabled(boolean on);
+ void setEnabled(boolean on, boolean visible);
void setMute(boolean on);
void enableMute(boolean enabled);
void setHold(boolean on);
@@ -340,6 +391,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
void showSwap(boolean show);
void showAddCall(boolean show);
void enableAddCall(boolean enabled);
+ void enableAddParticipant(boolean show);
void displayDialpad(boolean on);
boolean isDialpadVisible();
void setAudio(int mode);
@@ -348,5 +400,16 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
void showGenericMergeButton();
void hideExtraRow();
void displayManageConferencePanel(boolean on);
+ void displayModifyCallOptions(int callId);
+ void enableModifyCall(boolean enabled);
+ void showModifyCall(boolean show);
+ }
+
+ @Override
+ public void onActiveSubChanged(int subscription) {
+ InCallState state = InCallPresenter.getInstance()
+ .getPotentialStateFromCallList(CallList.getInstance());
+
+ onStateChange(state, CallList.getInstance());
}
}
diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java
index 0d26b82c..02faa24e 100644
--- a/src/com/android/incallui/CallCardFragment.java
+++ b/src/com/android/incallui/CallCardFragment.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");
@@ -22,6 +26,9 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import static android.telephony.TelephonyManager.SIM_STATE_ABSENT;
+import android.telephony.MSimTelephonyManager;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -33,6 +40,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.services.telephony.common.AudioMode;
import com.android.services.telephony.common.Call;
import java.util.List;
@@ -54,6 +62,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
private View mProviderInfo;
private TextView mProviderLabel;
private TextView mProviderNumber;
+ private TextView mSubscriptionId;
private ViewGroup mSupplementaryInfoContainer;
// Secondary caller info
@@ -65,6 +74,25 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
// Cached DisplayMetrics density.
private float mDensity;
+ private VideoCallPanel mVideoCallPanel;
+ private boolean mAudioDeviceInitialized = false;
+
+ // Constants for TelephonyProperties.PROPERTY_IMS_AUDIO_OUTPUT property.
+ // Currently, the default audio output is headset if connected, bluetooth
+ // if connected, speaker/earpiece for video/voice call.
+ private static final int IMS_AUDIO_OUTPUT_DEFAULT = 0;
+ private static final int IMS_AUDIO_OUTPUT_DISABLE_SPEAKER = 1;
+
+ /**
+ * Controls audio route for VT calls.
+ * 0 - Use the default audio routing strategy.
+ * 1 - Disable the speaker. Route the audio to Headset or Bloutooth
+ * or Earpiece, based on the default audio routing strategy.
+ * This property is for testing purpose only.
+ */
+ static final String PROPERTY_IMS_AUDIO_OUTPUT =
+ "persist.radio.ims.audio.output";
+
@Override
CallCardPresenter.CallCardUi getUi() {
return this;
@@ -115,8 +143,10 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
mProviderInfo = view.findViewById(R.id.providerInfo);
mProviderLabel = (TextView) view.findViewById(R.id.providerLabel);
mProviderNumber = (TextView) view.findViewById(R.id.providerAddress);
+ mSubscriptionId = (TextView) view.findViewById(R.id.subId);
mSupplementaryInfoContainer =
(ViewGroup) view.findViewById(R.id.supplementary_info_container);
+ mVideoCallPanel = (VideoCallPanel) view.findViewById(R.id.videoCallPanel);
}
@Override
@@ -172,14 +202,15 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
} else {
mNumberLabel.setVisibility(View.GONE);
}
-
}
@Override
public 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 isForwarded, boolean isVideo) {
Log.d(this, "Setting primary call");
+
if (isConference) {
name = getConferenceString(isGeneric);
photo = getConferencePhoto(isGeneric);
@@ -194,9 +225,26 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
// Set the label (Mobile, Work, etc)
setPrimaryLabel(label);
- showInternetCallLabel(isSipCall);
+ showCallTypeLabel(isSipCall, isForwarded);
+ MSimTelephonyManager tm = MSimTelephonyManager.getDefault();
+ int numPhones = tm.getPhoneCount();
+
+ if (tm.isMultiSimEnabled() && !(tm.getMultiSimConfiguration()
+ == MSimTelephonyManager.MultiSimVariants.DSDA)) {
+ int subscription = getPresenter().getActiveSubscription();
+ String operatorName = tm.getSimState(subscription) != SIM_STATE_ABSENT
+ ? tm.getNetworkOperatorName(subscription) : getString(R.string.sub_no_sim);
+ String sub = getString(R.string.multi_sim_entry_format, operatorName,
+ subscription + 1);
+
+ if (subscription != -1) {
+ showSubscriptionInfo(sub);
+ }
+ }
- setDrawableToImageView(mPhoto, photo);
+ if (! isVideo) {
+ setDrawableToImageView(mPhoto, photo);
+ }
}
@Override
@@ -234,11 +282,20 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
@Override
public void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn,
- String gatewayLabel, String gatewayNumber) {
+ String gatewayLabel, String gatewayNumber, boolean isHeldRemotely, int callType) {
String callStateLabel = null;
+ // If this is a video call then update the state of the VideoCallPanel
+ if (CallUtils.isVideoCall(callType)) {
+ updateVideoCallState(state, callType);
+ } else {
+ // This will hide the VideoCallPanel for any non VT/ non VS call or
+ // downgrade scenarios
+ hideVideoCallWidgets();
+ }
+
// States other than disconnected not yet supported
- callStateLabel = getCallStateLabelFromState(state, cause);
+ callStateLabel = getCallStateLabelFromState(state, cause, isHeldRemotely);
Log.v(this, "setCallState " + callStateLabel);
Log.v(this, "DisconnectCause " + cause);
@@ -290,12 +347,13 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
}
}
- private void showInternetCallLabel(boolean show) {
- if (show) {
- final String label = getView().getContext().getString(
- R.string.incall_call_type_label_sip);
+ private void showCallTypeLabel(boolean isSipCall, boolean isForwarded) {
+ if (isSipCall) {
+ mCallTypeLabel.setVisibility(View.VISIBLE);
+ mCallTypeLabel.setText(R.string.incall_call_type_label_sip);
+ } else if (isForwarded) {
mCallTypeLabel.setVisibility(View.VISIBLE);
- mCallTypeLabel.setText(label);
+ mCallTypeLabel.setText(R.string.incall_call_type_label_forwarded);
} else {
mCallTypeLabel.setVisibility(View.GONE);
}
@@ -314,6 +372,15 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
}
}
+ private void showSubscriptionInfo(String subString) {
+ if (!TextUtils.isEmpty(subString)) {
+ mSubscriptionId.setText(subString);
+ mSubscriptionId.setVisibility(View.VISIBLE);
+ } else {
+ mSubscriptionId.setVisibility(View.GONE);
+ }
+ }
+
private void setDrawableToImageView(ImageView view, Drawable photo) {
if (photo == null) {
photo = view.getResources().getDrawable(R.drawable.picture_unknown);
@@ -360,7 +427,8 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
* Gets the call state label based on the state of the call and
* cause of disconnect
*/
- private String getCallStateLabelFromState(int state, Call.DisconnectCause cause) {
+ private String getCallStateLabelFromState(int state, Call.DisconnectCause cause,
+ boolean isHeldRemotely) {
final Context context = getView().getContext();
String callStateLabel = null; // Label to display as part of the call banner
@@ -370,11 +438,14 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
} else if (Call.State.ACTIVE == state) {
// We normally don't show a "call state label" at all in
// this state (but see below for some special cases).
-
+ if (isHeldRemotely) {
+ callStateLabel = context.getString(R.string.card_title_waiting_call);
+ }
} else if (Call.State.ONHOLD == state) {
callStateLabel = context.getString(R.string.card_title_on_hold);
} else if (Call.State.DIALING == state) {
- callStateLabel = context.getString(R.string.card_title_dialing);
+ callStateLabel = context.getString(isHeldRemotely
+ ? R.string.card_title_dialing_waiting : R.string.card_title_dialing);
} else if (Call.State.REDIALING == state) {
callStateLabel = context.getString(R.string.card_title_redialing);
} else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) {
@@ -509,6 +580,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
dispatchPopulateAccessibilityEvent(event, mPrimaryName);
dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
+ dispatchPopulateAccessibilityEvent(event, mSubscriptionId);
dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
return;
@@ -524,4 +596,149 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
eventText.add(null);
}
}
+
+ /**
+ * Updates the VideoCallPanel based on the current state of the call
+ * TODO: Move to a separate file.
+ * @param call
+ */
+ private void updateVideoCallState(int callState, int callType) {
+ log(" - Videocall.state: " + callState);
+
+ // Null check
+ if (mVideoCallPanel == null) {
+ loge("VideocallPanel is null");
+ return;
+ }
+ switch (callState) {
+ case Call.State.INCOMING:
+ break;
+
+ case Call.State.DIALING:
+ case Call.State.REDIALING:
+ case Call.State.ACTIVE:
+ initVideoCall(callType);
+ showVideoCallWidgets(callType);
+ break;
+
+ case Call.State.DISCONNECTING:
+ case Call.State.DISCONNECTED:
+ case Call.State.ONHOLD:
+ case Call.State.IDLE:
+ case Call.State.CALL_WAITING:
+ hideVideoCallWidgets();
+ break;
+
+ default:
+ Log.e(this, "videocall: updateVideoCallState in bad state:" + callState);
+ hideVideoCallWidgets();
+ break;
+ }
+ }
+
+ /**
+ * If this is a video call then hide the photo widget and show the video
+ * call panel
+ */
+ private void showVideoCallWidgets(int callType) {
+
+ if (isPhotoVisible()) {
+ log("show videocall widget");
+ mPhoto.setVisibility(View.GONE);
+ }
+
+ mVideoCallPanel.setVisibility(View.VISIBLE);
+ mVideoCallPanel.setPanelElementsVisibility(callType);
+ mVideoCallPanel.startOrientationListener(true);
+ }
+
+ /**
+ * Hide the video call widget and restore the photo widget and reset
+ * mAudioDeviceInitialized
+ */
+ private void hideVideoCallWidgets() {
+ mAudioDeviceInitialized = false;
+
+ if ((mVideoCallPanel != null) && (mVideoCallPanel.getVisibility() == View.VISIBLE)) {
+ log("Hide videocall widget");
+
+ mPhoto.setVisibility(View.VISIBLE);
+ mVideoCallPanel.setVisibility(View.GONE);
+ mVideoCallPanel.setCameraNeeded(false);
+ mVideoCallPanel.startOrientationListener(false);
+ }
+ }
+
+ /**
+ * Initializes the video call widgets if not already initialized
+ */
+ private void initVideoCall(int callType) {
+ /*
+ * 1. Speaker state is updated only at the beginning of a video call 2.
+ * For MO video call, speaker update happens in dialing state 3. For MT
+ * video call, it happens in active state 4. Speaker state not changed
+ * during a call when VOLTE<->VT 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 949d7189..5697aaca 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.
@@ -150,6 +156,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
Log.d(this, "Secondary call: " + secondary);
final boolean primaryChanged = !areCallsSame(mPrimary, primary);
+ final boolean primaryForwardedChanged = isForwarded(mPrimary) != isForwarded(primary);
final boolean secondaryChanged = !areCallsSame(mSecondary, secondary);
mSecondary = secondary;
mPrimary = primary;
@@ -158,9 +165,11 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
// primary call has changed
mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext,
mPrimary.getIdentification(), mPrimary.getState() == Call.State.INCOMING);
- updatePrimaryDisplayInfo(mPrimaryContactInfo, isConference(mPrimary));
maybeStartSearch(mPrimary, true);
}
+ if ((primaryChanged || primaryForwardedChanged) && mPrimary != null) {
+ updatePrimaryDisplayInfo(mPrimaryContactInfo, isConference(mPrimary));
+ }
if (mSecondary == null) {
// Secondary call may have ended. Update the ui.
@@ -185,13 +194,16 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
// 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(), mPrimary.isHeldRemotely(), callType);
} else {
- ui.setCallState(Call.State.IDLE, Call.DisconnectCause.UNKNOWN, false, null, null);
+ ui.setCallState(Call.State.IDLE, Call.DisconnectCause.UNKNOWN,
+ false, null, null, false, callType);
}
}
@@ -201,7 +213,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
final boolean bluetoothOn = (AudioMode.BLUETOOTH == mode);
getUi().setCallState(mPrimary.getState(), mPrimary.getDisconnectCause(), bluetoothOn,
- getGatewayLabel(), getGatewayNumber());
+ getGatewayLabel(), getGatewayNumber(), mPrimary.isHeldRemotely(),
+ CallUtils.getCallType(mPrimary));
}
}
@@ -236,7 +249,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
// 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) {
@@ -272,7 +286,8 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
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);
@@ -290,6 +305,10 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
return call != null && call.can(Capabilities.GENERIC_CONFERENCE);
}
+ private static boolean isForwarded(Call call) {
+ return call != null && call.isForwarded();
+ }
+
private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary,
boolean isConference) {
if (isPrimary) {
@@ -354,14 +373,18 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
final boolean isGenericConf = isGenericConference(mPrimary);
+ final boolean isForwarded = isForwarded(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, isForwarded, isVideo);
} else {
- ui.setPrimary(null, null, false, null, null, isConference, isGenericConf, false);
+ ui.setPrimary(null, null, false, null, null, isConference,
+ isGenericConf, false, isForwarded, isVideo);
}
}
@@ -458,16 +481,21 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
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 isForwarded, 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, boolean isHeldRemotely, int callType);
void setPrimaryCallElapsedTime(boolean show, String duration);
void setPrimaryName(String name, boolean nameIsNumber);
void setPrimaryImage(Drawable image);
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..3102c3e5 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;
@@ -230,6 +231,60 @@ public class CallCommandClient {
}
}
+ public void hangupWithReason(int callId, String userUri, boolean mpty,
+ int failCause, String errorInfo) {
+ if (mCommandService == null) {
+ Log.e(this, "Cannot hangupWithReason(); CallCommandService == null");
+ return;
+ }
+ try {
+ Log.v(this, "hangupWithReason() ");
+ mCommandService.hangupWithReason(callId, userUri, mpty,
+ failCause, errorInfo);
+ } catch (RemoteException e) {
+ Log.e(this, "Error on hangupWithReason().", e);
+ }
+ }
+
+ public void answerCallWithCallType(int callId,int callType){
+ if (mCommandService == null) {
+ Log.e(this, "Cannot acceptCall(); CallCommandService == null");
+ return;
+ }
+ try {
+ Log.v(this, "acceptCall() " );
+ mCommandService.answerCallWithCallType(callId,callType);
+ } catch (RemoteException e) {
+ Log.e(this, "Error on acceptCall().", e);
+ }
+ }
+
+ public void modifyCallInitiate(int callId, int callType) {
+ if (mCommandService == null) {
+ Log.e(this, "Cannot modifyCall(); CallCommandService == null");
+ return;
+ }
+ try {
+ Log.v(this, "modifyCall(), callId=" + callId + " callType=" + callType);
+ mCommandService.modifyCallInitiate(callId, callType);
+ } 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");
@@ -243,4 +298,44 @@ public class CallCommandClient {
}
}
+ public void blacklistAndHangup(int callId) {
+ if (mCommandService == null) {
+ Log.e(this, "Cannot blacklistAndHangup(); CallCommandService == null");
+ return;
+ }
+ try {
+ mCommandService.blacklistAndHangup(callId);
+ } catch (RemoteException e) {
+ Log.e(this, "Error on blacklistAndHangup().", e);
+ }
+ }
+
+ 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..7a206317 100644
--- a/src/com/android/incallui/CallHandlerService.java
+++ b/src/com/android/incallui/CallHandlerService.java
@@ -48,8 +48,10 @@ 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 ON_UNSOL_CALLMODIFY = 12;
- private static final int LARGEST_MSG_ID = ON_DESTROY;
+ private static final int LARGEST_MSG_ID = ON_ACTIVE_SUB_CHANGE;
private CallList mCallList;
@@ -59,6 +61,8 @@ public class CallHandlerService extends Service {
private AudioModeProvider mAudioModeProvider;
private boolean mServiceStarted = false;
+ private final String LOG_TAG = "CallHandlerService";
+
@Override
public void onCreate() {
Log.i(TAG, "onCreate");
@@ -184,6 +188,22 @@ public class CallHandlerService extends Service {
mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_POST_CHAR_WAIT, callId, 0,
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));
+ }
+
};
private void doStart(ICallCommandService service) {
@@ -227,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.
@@ -302,6 +335,16 @@ public class CallHandlerService extends Service {
case ON_DESTROY:
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);
+ 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 dee27557..9b942ed1 100644
--- a/src/com/android/incallui/CallList.java
+++ b/src/com/android/incallui/CallList.java
@@ -24,6 +24,8 @@ import com.google.common.base.Preconditions;
import android.os.Handler;
import android.os.Message;
+import android.telephony.MSimTelephonyManager;
+import com.android.internal.telephony.MSimConstants;
import com.android.services.telephony.common.Call;
import com.android.services.telephony.common.Call.DisconnectCause;
@@ -44,6 +46,7 @@ public class CallList {
private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000;
private static final int EVENT_DISCONNECTED_TIMEOUT = 1;
+ private static final int EVENT_NOTIFY_CHANGE = 2;
private static CallList sInstance = new CallList();
@@ -54,6 +57,9 @@ public class CallList {
private final HashMap<Integer, List<CallUpdateListener>> mCallUpdateListenerMap = Maps
.newHashMap();
+ private int mSubscription = 0;
+ private final ArrayList<ActiveSubChangeListener> mActiveSubChangeListeners =
+ Lists.newArrayList();
/**
* Static singleton accessor method.
@@ -74,6 +80,8 @@ public class CallList {
public void onUpdate(Call call) {
Log.d(this, "onUpdate - ", call);
+ updateActiveSuscription();
+
updateCallInMap(call);
notifyListenersOfChange();
}
@@ -101,6 +109,8 @@ public class CallList {
public void onIncoming(Call call, List<String> textMessages) {
Log.d(this, "onIncoming - " + call);
+ updateActiveSuscription();
+
updateCallInMap(call);
updateCallTextMap(call, textMessages);
@@ -115,6 +125,8 @@ public class CallList {
public void onUpdate(List<Call> callsToUpdate) {
Log.d(this, "onUpdate(...)");
+ updateActiveSuscription();
+
Preconditions.checkNotNull(callsToUpdate);
for (Call call : callsToUpdate) {
Log.d(this, "\t" + call);
@@ -283,6 +295,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()) {
@@ -350,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);
@@ -438,6 +466,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;
@@ -478,4 +513,124 @@ 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 = " + 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;
+ }
+
+ /**
+ * 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.
+ */
+ 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/CallUtils.java b/src/com/android/incallui/CallUtils.java
new file mode 100644
index 00000000..9fb488fd
--- /dev/null
+++ b/src/com/android/incallui/CallUtils.java
@@ -0,0 +1,119 @@
+/* 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 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) {
+ return call != null ? call.getCallDetails() : null;
+ }
+
+ private static CallDetails getCallModifyDetails(Call call) {
+ return call != null ? call.getCallModifyDetails() : null;
+ }
+
+ public static boolean isVideoCall(Call call) {
+ if (call == null || call.getCallDetails() == null) {
+ return false;
+ }
+ 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..7c0a063e
--- /dev/null
+++ b/src/com/android/incallui/CameraHandler.java
@@ -0,0 +1,350 @@
+/* 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.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.WindowManager;
+
+/**
+ * The class is used to hold an {@code android.hardware.Camera} instance.
+ * <p>
+ * The {@code open()} and {@code release()} calls are similar to the ones in
+ * {@code android.hardware.Camera}.
+ */
+
+public class CameraHandler {
+ public static final int CAMERA_UNKNOWN = -1;
+ private static final String TAG = "VideoCallCameraHandler";
+ private static final boolean DBG = true;
+ 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 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
+ */
+ 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);
+ }
+ }
+ mDpm = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ // Get display rotation
+ mWindowManager = (WindowManager) mContext.getSystemService(
+ Context.WINDOW_SERVICE);
+ }
+
+ /**
+ * 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 {
+ if (mDpm == null) {
+ throw new Exception("DevicePolicyManager not available");
+ }
+
+ if (mDpm.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 = ImsCamera.open(cameraId);
+ mCameraId = cameraId;
+ } catch (Exception e) {
+ loge("fail to connect Camera" + e);
+ throw e;
+ }
+ }
+ 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);
+
+ 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");
+ if (mCameraState == CameraState.PREVIEW_STARTED) {
+ mCameraDevice.stopPreview();
+ }
+ mCameraDevice.release();
+ }
+ mCameraDevice = 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.stopPreview();
+ }
+ mCameraState = CameraState.PREVIEW_STOPPED;
+ }
+
+ public void startCameraRecording() {
+ if (mCameraDevice != null && mCameraState == CameraState.PREVIEW_STARTED) {
+ mCameraDevice.startRecording();
+ }
+ }
+
+ public void stopCameraRecording() {
+ if (mCameraDevice != null) {
+ mCameraDevice.stopRecording();
+ }
+ }
+
+ /**
+ * 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;
+ mCameraDevice.setPreviewTexture(surfaceTexture);
+
+ }
+
+ /**
+ * Set the texture view for the camera
+ *
+ * @param textureView
+ */
+ public void setDisplay(TextureView textureView) {
+ setDisplay(textureView.getSurfaceTexture());
+ }
+
+ /**
+ * 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;
+
+ if (mWindowManager == null) {
+ loge("WindowManager not available");
+ return;
+ }
+
+ rotation = mWindowManager.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);
+ }
+
+ public ImsCamera getImsCameraInstance() {
+ return mCameraDevice;
+ }
+
+ 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/ConferenceManagerFragment.java b/src/com/android/incallui/ConferenceManagerFragment.java
index 75ff1769..119b5f35 100644
--- a/src/com/android/incallui/ConferenceManagerFragment.java
+++ b/src/com/android/incallui/ConferenceManagerFragment.java
@@ -155,6 +155,17 @@ public class ConferenceManagerFragment
});
}
+ @Override
+ public void setupEndButtonForRowWithUrl(final int rowId, final String url) {
+ View endButton = mConferenceCallList[rowId].findViewById(R.id.conferenceCallerDisconnect);
+ endButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getPresenter().endConferenceConnectionUrl(rowId, url);
+ }
+ });
+ }
+
@Override
public final void setCanSeparateButtonForRow(final int rowId, boolean canSeparate) {
final View separateButton = mConferenceCallList[rowId].findViewById(
diff --git a/src/com/android/incallui/ConferenceManagerPresenter.java b/src/com/android/incallui/ConferenceManagerPresenter.java
index 1ba88cbd..0535bda1 100644
--- a/src/com/android/incallui/ConferenceManagerPresenter.java
+++ b/src/com/android/incallui/ConferenceManagerPresenter.java
@@ -22,6 +22,7 @@ import com.android.incallui.ContactInfoCache.ContactCacheEntry;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.services.telephony.common.Call;
+import com.android.services.telephony.common.CallDetails;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSortedSet;
@@ -37,7 +38,9 @@ public class ConferenceManagerPresenter
private int mNumCallersInConference;
private Integer[] mCallerIds;
+ private String[] mParticipantList;
private Context mContext;
+ private static String LOG_TAG = "ConferenceManagerPresenter";
@Override
public void onUiReady(ConferenceManagerUi ui) {
@@ -79,10 +82,34 @@ public class ConferenceManagerPresenter
update(callList);
}
- private void update(CallList callList) {
- mCallerIds = null;
+ private boolean isImsCall(Call call) {
+ return call != null && call.getCallDetails() != null
+ && call.getCallDetails().getCallDomain() == CallDetails.CALL_DOMAIN_PS;
+ }
+
+ private void initParticipantList(CallList callList) {
+ mParticipantList = null;
+ Call call = callList.getActiveOrBackgroundCall();
+
+ if (isImsCall(call)) {
+ String[] confParticipantList = call.getCallDetails().getConfParticipantList();
+ // If conference refresh info xml is present use that information
+ if (confParticipantList != null
+ && confParticipantList.length > 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/ContactInfoCache.java b/src/com/android/incallui/ContactInfoCache.java
index 448de7fe..4bbf8fad 100644
--- a/src/com/android/incallui/ContactInfoCache.java
+++ b/src/com/android/incallui/ContactInfoCache.java
@@ -28,6 +28,8 @@ import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.lookup.ReverseLookupThread;
import com.android.incallui.service.PhoneNumberService;
import com.android.incalluibind.ServiceFactory;
import com.android.services.telephony.common.Call;
@@ -169,12 +171,17 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete
sendInfoNotifications(callId, cacheEntry);
if (didLocalLookup) {
- if (!callerInfo.contactExists && cacheEntry.name == null &&
- mPhoneNumberService != null) {
+ if (!callerInfo.contactExists && cacheEntry.name == null) {
Log.d(TAG, "Contact lookup. Local contacts miss, checking remote");
- final PhoneNumberServiceListener listener = new PhoneNumberServiceListener(callId);
- mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener,
- isIncoming);
+ if (mPhoneNumberService != null) {
+ final PhoneNumberServiceListener listener =
+ new PhoneNumberServiceListener(callId);
+ mPhoneNumberService.getPhoneNumberInfo(cacheEntry.number, listener, listener,
+ isIncoming);
+ } else {
+ final ReverseLookupListener listener = new ReverseLookupListener(callId);
+ ReverseLookupThread.performLookup(mContext, cacheEntry.number, listener);
+ }
} else if (cacheEntry.personUri != null) {
Log.d(TAG, "Contact lookup. Local contact found, starting image load");
// Load the image with a callback to update the image state.
@@ -195,6 +202,57 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete
}
}
+ public class ReverseLookupListener {
+ private final int mCallId;
+
+ ReverseLookupListener(int callId) {
+ mCallId = callId;
+ }
+
+ public void onLookupComplete(final ContactInfo info) {
+ if (info == null) {
+ Log.d(TAG, "Reverse lookup returned no result.");
+ clearCallbacks(mCallId);
+ return;
+ }
+
+ ContactCacheEntry entry = new ContactCacheEntry();
+ entry.name = info.name;
+ entry.number = info.number;
+ if (info.type == Phone.TYPE_CUSTOM) {
+ entry.label = info.label;
+ } else {
+ final CharSequence typeStr = Phone.getTypeLabel(
+ mContext.getResources(), info.type, info.label);
+ entry.label = typeStr == null ? null : typeStr.toString();
+ }
+
+ final ContactCacheEntry oldEntry = mInfoMap.get(mCallId);
+ if (oldEntry != null) {
+ // Location is only obtained from local lookup so persist
+ // the value for remote lookups. Once we have a name this
+ // field is no longer used; it is persisted here in case
+ // the UI is ever changed to use it.
+ entry.location = oldEntry.location;
+ }
+
+ // Add the contact info to the cache.
+ mInfoMap.put(mCallId, entry);
+ sendInfoNotifications(mCallId, entry);
+
+ // If there is no image then we should not expect another callback.
+ if (info.photoUri == null) {
+ // We're done, so clear callbacks
+ clearCallbacks(mCallId);
+ }
+ }
+
+ public void onImageFetchComplete(Bitmap bitmap) {
+ onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null,
+ bitmap, (Integer) mCallId);
+ }
+ }
+
class PhoneNumberServiceListener implements PhoneNumberService.NumberLookupListener,
PhoneNumberService.ImageLookupListener {
private final int mCallId;
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/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/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.
+ * <p>
+ * 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/InCallActivity.java b/src/com/android/incallui/InCallActivity.java
index c34d8547..8e80c9ff 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;
@@ -27,8 +35,11 @@ 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.telephony.MSimTelephonyManager;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
@@ -44,13 +55,14 @@ 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} */
private boolean mShowDialpadRequested;
@@ -61,6 +73,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".
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
@@ -86,6 +103,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);
}
@@ -140,7 +162,7 @@ public class InCallActivity extends Activity {
return mIsForegroundActivity;
}
- private boolean hasPendingErrorDialog() {
+ protected boolean hasPendingErrorDialog() {
return mDialog != null;
}
/**
@@ -160,6 +182,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.
@@ -193,6 +220,17 @@ public class InCallActivity extends Activity {
public void onBackPressed() {
Log.d(this, "onBackPressed()...");
+ if (mAnswerFragment.isVisible()) {
+ // The Back key, just like the Home key, is always disabled
+ // while an incoming call is ringing. (The user *must* either
+ // answer or reject the call before leaving the incoming-call
+ // screen.)
+ Log.d(this, "BACK key while ringing: ignored");
+
+ // And consume this event; *don't* call super.onBackPressed().
+ return;
+ }
+
// BACK is also used to exit out of any "special modes" of the
// in-call UI:
@@ -309,6 +347,14 @@ public class InCallActivity extends Activity {
InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config);
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) { // On touch.
+ if (InCallPresenter.getInstance().getProximitySensor().isScreenOffByProximity())
+ return true;
+
+ return super.dispatchTouchEvent(event);
+ }
+
public CallButtonFragment getCallButtonFragment() {
return mCallButtonFragment;
}
@@ -350,7 +396,7 @@ public class InCallActivity extends Activity {
}
}
- private void initializeInCall() {
+ protected void initializeInCall() {
if (mCallButtonFragment == null) {
mCallButtonFragment = (CallButtonFragment) getFragmentManager()
.findFragmentById(R.id.callButtonFragment);
@@ -426,6 +472,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<CharSequence> items = new ArrayList<CharSequence>();
+ final ArrayList<Integer> itemToCallType = new ArrayList<Integer>();
+ 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("Videocall: ModifyCall: upgrade/downgrade 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");
@@ -455,7 +628,12 @@ public class InCallActivity extends Activity {
mDialog.dismiss();
mDialog = null;
}
+
mAnswerFragment.dismissPendingDialogues();
+ if (mModifyCallPromptDialog != null) {
+ mModifyCallPromptDialog.dismiss();
+ mModifyCallPromptDialog = null;
+ }
}
/**
@@ -498,6 +676,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;
@@ -507,4 +691,16 @@ 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);
+ }
+
+ public void updateDsdaTab() {
+ Log.e(this, "updateDsdaTab : Not supported ");
+ }
}
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 14026212..ff1b963f 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");
@@ -16,15 +20,19 @@
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;
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;
+import com.android.services.telephony.common.CallDetails;
import com.google.common.collect.Lists;
import java.util.ArrayList;
@@ -55,6 +63,31 @@ 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";
+
+ /**
+ * 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
@@ -64,6 +97,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();
@@ -111,6 +146,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");
}
@@ -132,12 +170,82 @@ 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();
}
}
/**
+ * 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);
+ 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);
+ }
+ }
+
+ /**
+ * 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);
+ 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(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);
+ try {
+ if (isUserConsentRequired(proposedCallType, currCallType)) {
+ if (mInCallActivity != null) {
+ mInCallActivity.displayModifyCallConsentDialog(call);
+ } else {
+ Log.e(this, "VideoCall: onMoifyCallRequest: InCallActivity is null.");
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Log.e(this, "VideoCall: onModifyCallRequest failed. ", e);
+ }
+ }
+
+ 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
* the tear-down process.
@@ -232,6 +340,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;
@@ -242,6 +352,11 @@ public class InCallPresenter implements CallList.Listener {
listener.onStateChange(mInCallState, callList);
}
+ if (MSimTelephonyManager.getDefault().getMultiSimConfiguration()
+ == MSimTelephonyManager.MultiSimVariants.DSDA && (mInCallActivity != null)) {
+ mInCallActivity.updateDsdaTab();
+ }
+
if (isActivityStarted()) {
final boolean hasCall = callList.getActiveOrBackgroundCall() != null ||
callList.getOutgoingCall() != null;
@@ -258,6 +373,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;
@@ -269,6 +386,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();
+ }
}
/**
@@ -635,7 +757,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();
}
@@ -701,7 +830,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);
}
@@ -709,6 +843,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.
*/
@@ -753,4 +907,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/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) {
diff --git a/src/com/android/incallui/MSimAnswerFragment.java b/src/com/android/incallui/MSimAnswerFragment.java
new file mode 100644
index 00000000..92ff5c2c
--- /dev/null
+++ b/src/com/android/incallui/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<MSimAnswerPresenter,
+ MSimAnswerPresenter.AnswerUi>
+ 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<String> 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<String> textResponses) {
+ final ArrayList<String> textResponsesForDisplay = new ArrayList<String>(textResponses);
+
+ textResponsesForDisplay.add(getResources().getString(
+ R.string.respond_via_sms_custom_message));
+ mTextResponsesAdapter = new ArrayAdapter<String>(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/MSimAnswerPresenter.java b/src/com/android/incallui/MSimAnswerPresenter.java
new file mode 100644
index 00000000..375b05de
--- /dev/null
+++ b/src/com/android/incallui/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<MSimAnswerPresenter.AnswerUi>
+ 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<String> 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<String> 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<String> 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/MSimInCallActivity.java b/src/com/android/incallui/MSimInCallActivity.java
new file mode 100644
index 00000000..3f61a595
--- /dev/null
+++ b/src/com/android/incallui/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) {
+ }
+ }
+}
diff --git a/src/com/android/incallui/MediaHandler.java b/src/com/android/incallui/MediaHandler.java
new file mode 100644
index 00000000..95acd7b9
--- /dev/null
+++ b/src/com/android/incallui/MediaHandler.java
@@ -0,0 +1,361 @@
+/* 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.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;
+
+/**
+ * Provides an interface to handle the media part of the video telephony call
+ */
+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;
+
+ 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 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 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();
+
+ // 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
+ * event
+ */
+ private static int mNegotiatedHeight = 240;
+ private static int mNegotiatedWidth = 320;
+ private static int mUIOrientationMode = PORTRAIT_MODE;
+ private static short mNegotiatedFps = 20;
+
+ private int mPeerHeight = DEFAULT_HEIGHT;
+ private int mPeerWidth = DEFAULT_WIDTH;
+ private IMediaEventListener 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 constructor for MediaHandler
+ */
+ private MediaHandler() {
+ }
+
+ public interface IMediaEventListener {
+ void onParamReadyEvent();
+ void onDisplayModeEvent();
+ void onStartReadyEvent();
+ void onPeerResolutionChangeEvent();
+ }
+
+ 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) {
+ 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;
+ }
+
+ 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
+ * @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 Height
+ */
+ public synchronized static int getNegotiatedHeight() {
+ Log.d(TAG, "Negotiated Height = " + mNegotiatedHeight);
+ return mNegotiatedHeight;
+ }
+
+ /**
+ * Get Negotiated Width
+ */
+ public synchronized static int getNegotiatedWidth() {
+ Log.d(TAG, "Negotiated Width = " + mNegotiatedWidth);
+ return mNegotiatedWidth;
+ }
+
+ /**
+ * Get Negotiated Width
+ */
+ public int getUIOrientationMode() {
+ Log.d(TAG, "UI Orientation Mode = " + mUIOrientationMode);
+ return mUIOrientationMode;
+ }
+
+ public synchronized static short getNegotiatedFps() {
+ 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)}
+ */
+ private static void registerForMediaEvents(MediaHandler instance) {
+ Log.d(TAG, "Registering for Media Callback Events");
+ nativeRegisterForMediaEvents(instance);
+ }
+
+ public void setMediaEventListener(IMediaEventListener listener) {
+ mMediaEventListener = listener;
+ }
+
+ private void doOnMediaEvent(int eventId) {
+ switch (eventId) {
+ case PARAM_READY_EVT:
+ Log.d(TAG, "Received PARAM_READY_EVT. Updating negotiated values");
+ if (updatePreviewParams() && mMediaEventListener != null) {
+ 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) {
+ 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);
+ }
+ }
+
+ /**
+ * 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 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));
+ }
+
+ /**
+ * 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/ProximityListener.java b/src/com/android/incallui/ProximityListener.java
new file mode 100644
index 00000000..a5a79ff8
--- /dev/null
+++ b/src/com/android/incallui/ProximityListener.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2013 dimfish
+ *
+ * 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.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+public final class ProximityListener {
+ private static final String TAG = "ProximityListener";
+ private static final boolean DEBUG = true;
+ private static final boolean VDEBUG = false;
+
+ private static final float PROXIMITY_THRESHOLD = 5.0f;
+
+ private long mLastProximityEventTime;
+ private boolean mActive;
+
+ private SensorManager mSensorManager;
+ private Sensor mSensor;
+
+
+ public ProximityListener(Context context) {
+ mActive = false;
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ }
+
+ public void enable(boolean enable) {
+ if (DEBUG) Log.d(TAG, "enable(" + enable + ")");
+ synchronized (this) {
+ mActive = false;
+ if (enable) {
+ mSensorManager.registerListener(mSensorListener, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ mSensorManager.unregisterListener(mSensorListener);
+ }
+ }
+ }
+
+ public boolean isActive() {
+ return mActive;
+ }
+
+ SensorEventListener mSensorListener = new SensorEventListener() {
+ public void onSensorChanged(SensorEvent event) {
+ synchronized (this) {
+ float distance = event.values[0];
+ // compare against getMaximumRange to support sensors that only return 0 or 1
+ mActive = (distance >= 0.0 && distance < PROXIMITY_THRESHOLD &&
+ distance < mSensor.getMaximumRange());
+
+ if (VDEBUG) Log.d(TAG, "mProximityListener.onSensorChanged active: " + mActive);
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // ignore
+ }
+ };
+}
diff --git a/src/com/android/incallui/ProximitySensor.java b/src/com/android/incallui/ProximitySensor.java
index 607a54f8..f3a7a9c0 100644
--- a/src/com/android/incallui/ProximitySensor.java
+++ b/src/com/android/incallui/ProximitySensor.java
@@ -43,6 +43,7 @@ public class ProximitySensor implements AccelerometerListener.OrientationListene
private final PowerManager.WakeLock mProximityWakeLock;
private final AudioModeProvider mAudioModeProvider;
private final AccelerometerListener mAccelerometerListener;
+ private final ProximityListener mProximityListener;
private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
private boolean mUiShowing = false;
private boolean mIsPhoneOffhook = false;
@@ -64,6 +65,7 @@ public class ProximitySensor implements AccelerometerListener.OrientationListene
Log.d(this, "onCreate: mProximityWakeLock: ", mProximityWakeLock);
mAccelerometerListener = new AccelerometerListener(context, this);
+ mProximityListener = new ProximityListener(context);
mAudioModeProvider = audioModeProvider;
mAudioModeProvider.addListener(this);
}
@@ -72,6 +74,7 @@ public class ProximitySensor implements AccelerometerListener.OrientationListene
mAudioModeProvider.removeListener(this);
mAccelerometerListener.enable(false);
+ mProximityListener.enable(false);
if (mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
mProximityWakeLock.release();
@@ -102,6 +105,7 @@ public class ProximitySensor implements AccelerometerListener.OrientationListene
mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
mAccelerometerListener.enable(mIsPhoneOffhook);
+ mProximityListener.enable(mIsPhoneOffhook);
updateProximitySensorMode();
}
@@ -162,6 +166,10 @@ public class ProximitySensor implements AccelerometerListener.OrientationListene
return !mPowerManager.isScreenOn();
}
+ public boolean isScreenOffByProximity() {
+ return mProximityListener.isActive();
+ }
+
/**
* @return true if this device supports the "proximity sensor
* auto-lock" feature while in-call (see updateProximitySensorMode()).
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.
diff --git a/src/com/android/incallui/VideoCallManager.java b/src/com/android/incallui/VideoCallManager.java
new file mode 100644
index 00000000..f9bd0461
--- /dev/null
+++ b/src/com/android/incallui/VideoCallManager.java
@@ -0,0 +1,346 @@
+/* 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.content.Context;
+import android.graphics.SurfaceTexture;
+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 com.android.incallui.MediaHandler.IMediaEventListener;
+
+import java.io.IOException;
+
+/**
+ * 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 static final int INVALID_SIZE = -1;
+ private CameraHandler mCameraHandler;
+ private MediaHandler mMediaHandler;
+ private CvoHandler mCvoHandler;
+ private Handler mHandler = new Handler() {
+ @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;
+ 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 = 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
+ *
+ * @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 UI Orientation mode
+ */
+ public int getUIOrientationMode() {
+ return mMediaHandler.getUIOrientationMode();
+ }
+
+ /**
+ * Get negotiated FPS
+ */
+ public short getNegotiatedFps() {
+ return MediaHandler.getNegotiatedFps();
+ }
+
+ public boolean isCvoModeEnabled() {
+ return mMediaHandler.isCvoModeEnabled();
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 ImsCamera getImsCameraInstance() {
+ return mCameraHandler.getImsCameraInstance();
+ }
+
+ public void startCameraRecording() {
+ mCameraHandler.startCameraRecording();
+ }
+
+ public void stopCameraRecording() {
+ mCameraHandler.stopCameraRecording();
+ }
+
+ public void setMediaEventListener(VideoCallPanel.MediaEventListener 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);
+ }
+ }
+
+ 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
new file mode 100644
index 00000000..1f71c0f3
--- /dev/null
+++ b/src/com/android/incallui/VideoCallPanel.java
@@ -0,0 +1,651 @@
+/* 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.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+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;
+
+/**
+ * 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 static final int INVALID_SIZE = -1;
+
+ 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;
+ private final Resize mResize = new Resize();
+
+ private int mZoomMax;
+ private int mZoomValue; // The current zoom value
+
+ // Multiple cameras support
+ private int mNumberOfCameras;
+ private int mFrontCameraId;
+ 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;
+
+ // 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 MediaEventListener implements MediaHandler.IMediaEventListener {
+ @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.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
+ }
+
+ @Override
+ public void onPeerResolutionChangeEvent() {
+ if (DBG) log("onPeerResolutionChangeEvent");
+
+ if (mHeight != INVALID_SIZE && mWidth != INVALID_SIZE) {
+ resizeFarEndView();
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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.setMediaEventListener(new MediaEventListener());
+ mVideoCallManager.setCvoEventListener(new CvoListener());
+ }
+
+ 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) {
+ log("onSizeChanged");
+ log("Video Panel xNew=" + xNew + ", yNew=" + yNew + " xOld=" + xOld + " yOld=" + yOld);
+ if (xNew != xOld || yNew != yOld) {
+ post(mResize);
+ }
+ }
+
+ 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();
+ }
+
+ /**
+ * 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();
+ startPreviewAndRecording();
+ }
+
+ public boolean isCameraInitNeeded() {
+ if (DBG) {
+ log("isCameraInitNeeded mCameraNeeded=" + mCameraNeeded + " mCameraSurface= "
+ + mCameraSurface + " camera state = "
+ + mVideoCallManager.getCameraState());
+ }
+ return mCameraNeeded && mCameraSurface != null
+ && 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 disconnect and releases the camera
+ */
+ private void closeCamera() {
+ mVideoCallManager.closeCamera();
+ }
+
+ /**
+ * This method starts the camera preview and recording
+ */
+ private void startPreviewAndRecording() {
+ try {
+ mVideoCallManager.startCameraPreview(mCameraSurface);
+ mVideoCallManager.startCameraRecording();
+ } catch (IOException ioe) {
+ closeCamera();
+ loge("Exception startPreviewAndRecording, " + ioe.toString());
+ }
+ }
+
+ /**
+ * This method stops the camera recording and preview
+ */
+ private void stopRecordingAndPreview() {
+ mVideoCallManager.stopCameraRecording();
+ 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 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");
+ stopRecordingAndPreview();
+ 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) {
+ stopRecordingAndPreview();
+ 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() {
+ ImsCamera imsCamera = mVideoCallManager.getImsCameraInstance();
+ if (imsCamera == null) {
+ return;
+ }
+ if (!imsCamera.isZoomSupported()) {
+ mZoomControl.setVisibility(View.GONE); // Disable ZoomControl
+ return;
+ }
+
+ mZoomControl.setVisibility(View.VISIBLE); // Enable ZoomControl
+ 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(DEFAULT_CAMERA_ZOOM_VALUE);
+ 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;
+ ImsCamera imsCamera = mVideoCallManager.getImsCameraInstance();
+ // Set zoom
+ if (imsCamera.isZoomSupported()) {
+ imsCamera.setZoom(mZoomValue);
+ }
+ }
+
+ /**
+ * 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.
+ 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
+ imsCamera.setPreviewSize(LOOPBACK_MODE_WIDTH, LOOPBACK_MODE_HEIGHT);
+ } else {
+ log("Set Preview Size directly with negotiated Height = "
+ + mVideoCallManager.getNegotiatedHeight()
+ + " negotiated width= " + mVideoCallManager.getNegotiatedWidth());
+ imsCamera.setPreviewSize(mVideoCallManager.getNegotiatedWidth(),
+ mVideoCallManager.getNegotiatedHeight());
+ imsCamera.setPreviewFpsRange(mVideoCallManager.getNegotiatedFps());
+ }
+ } catch (RuntimeException e) {
+ loge("Error setting Camera preview size/fps exception=" + e);
+ }
+ }
+
+ 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) {
+ stopRecordingAndPreview();
+ 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;
+ }
+ }
+
+ /**
+ * This method resizes the camera preview based on the size of the
+ * VideoCallPanel
+ */
+ 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 = 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
+ */
+ 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 FarEnd to width:" + farEndWidth + ", height:" + farEndHeight);
+ }
+
+ ViewGroup.LayoutParams farEndViewLp = mFarEndView.getLayoutParams();
+ farEndViewLp.height = farEndHeight;
+ farEndViewLp.width = farEndWidth;
+
+ 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) {
+ stopRecordingAndPreview();
+ 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();
+ }
+ }
+
+ /**
+ * 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");
+ }
+ }
+
+ public void startOrientationListener(boolean start) {
+ mVideoCallManager.startOrientationListener(start);
+ }
+
+ 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();
+ }
+}