| /* Copyright (c) 2017-2021, 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.support.v4.app.FragmentManager; |
| import android.support.v4.os.UserManagerCompat; |
| import android.app.Activity; |
| import android.content.ActivityNotFoundException; |
| import android.content.Intent; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.Settings; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.telecom.Call.Details; |
| import android.telecom.VideoProfile; |
| import android.view.View; |
| |
| import com.android.dialer.common.LogUtil; |
| import com.android.incallui.call.CallList; |
| import com.android.dialer.util.CallUtil; |
| import com.android.incallui.call.DialerCall; |
| import com.android.incallui.call.state.DialerCallState; |
| import com.android.incallui.videotech.utils.VideoUtils; |
| import com.android.incallui.videotech.utils.SessionModificationState; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import org.codeaurora.ims.QtiCallConstants; |
| import org.codeaurora.ims.QtiImsExtListenerBaseImpl; |
| import org.codeaurora.ims.QtiImsExtConnector; |
| import org.codeaurora.ims.QtiImsExtManager; |
| import org.codeaurora.ims.QtiImsException; |
| import org.codeaurora.ims.utils.QtiImsExtUtils; |
| |
| public class BottomSheetHelper implements PrimaryCallTracker.PrimaryCallChangeListener, |
| InCallPresenter.InCallEventListener{ |
| |
| private ConcurrentHashMap<String,Boolean> moreOptionsMap; |
| private ExtBottomSheetFragment moreOptionsSheet; |
| private boolean mIsHideMe = false; |
| private Context mContext; |
| private DialerCall mCall; |
| private PrimaryCallTracker mPrimaryCallTracker; |
| private Resources mResources; |
| private static BottomSheetHelper mHelper; |
| private boolean mHasSentCancelUpgradeRequest = false; |
| private AlertDialog callTransferDialog; |
| private AlertDialog mCancelModifyCallDialog; |
| private QtiImsExtConnector mQtiImsExtConnector; |
| private QtiImsExtManager mQtiImsExtManager; |
| private static final int BLIND_TRANSFER = 0; |
| private static final int ASSURED_TRANSFER = 1; |
| private static final int CONSULTATIVE_TRANSFER = 2; |
| |
| private QtiImsExtListenerBaseImpl imsInterfaceListener = |
| new QtiImsExtListenerBaseImpl() { |
| |
| /* Handles cancel call modify response */ |
| @Override |
| public void receiveCancelModifyCallResponse(int phoneId, int result) { |
| LogUtil.w("BottomSheetHelper.receiveCancelModifyCallResponse", "result: " + result); |
| mHasSentCancelUpgradeRequest = false; |
| maybeUpdateCancelModifyCallInMap(); |
| } |
| }; |
| |
| private AlertDialog modifyCallDialog; |
| private static final int INVALID_INDEX = -1; |
| |
| private BottomSheetHelper() { |
| LogUtil.d("BottomSheetHelper"," "); |
| } |
| |
| public static BottomSheetHelper getInstance() { |
| if (mHelper == null) { |
| mHelper = new BottomSheetHelper(); |
| } |
| return mHelper; |
| } |
| |
| private void createQtiImsExtConnector(Context context) { |
| try { |
| mQtiImsExtConnector = new QtiImsExtConnector(context, |
| new QtiImsExtConnector.IListener() { |
| @Override |
| public void onConnectionAvailable(QtiImsExtManager qtiImsExtManager) { |
| mQtiImsExtManager = qtiImsExtManager; |
| } |
| @Override |
| public void onConnectionUnavailable() { |
| mQtiImsExtManager = null; |
| } |
| }); |
| } catch (QtiImsException e) { |
| LogUtil.e("BottomSheetHelper.createQtiImsExtConnector", |
| "Unable to create QtiImsExtConnector"); |
| } |
| } |
| |
| public void setUp(Context context) { |
| LogUtil.d("BottomSheetHelper","setUp"); |
| mContext = context; |
| createQtiImsExtConnector(context); |
| mQtiImsExtConnector.connect(); |
| mResources = context.getResources(); |
| final String[][] moreOptions = getMoreOptionsFromRes(R.array.bottom_sheet_more_options); |
| moreOptionsMap = prepareSheetOptions(moreOptions); |
| mPrimaryCallTracker = new PrimaryCallTracker(); |
| InCallPresenter.getInstance().addListener(mPrimaryCallTracker); |
| InCallPresenter.getInstance().addIncomingCallListener(mPrimaryCallTracker); |
| InCallPresenter.getInstance().addInCallEventListener(this); |
| mPrimaryCallTracker.addListener(this); |
| } |
| |
| public void tearDown() { |
| LogUtil.d("BottomSheetHelper","tearDown"); |
| InCallPresenter.getInstance().removeListener(mPrimaryCallTracker); |
| InCallPresenter.getInstance().removeIncomingCallListener(mPrimaryCallTracker); |
| InCallPresenter.getInstance().removeInCallEventListener(this); |
| if (mPrimaryCallTracker != null) { |
| mPrimaryCallTracker.removeListener(this); |
| mPrimaryCallTracker = null; |
| } |
| mIsHideMe = false; |
| if (mQtiImsExtConnector != null) { |
| mQtiImsExtConnector.disconnect(); |
| mQtiImsExtConnector = null; |
| } |
| mQtiImsExtManager = null; |
| mContext = null; |
| mResources = null; |
| moreOptionsMap = null; |
| mHasSentCancelUpgradeRequest = false; |
| } |
| |
| public void updateMap() { |
| if (mPrimaryCallTracker == null) { |
| LogUtil.w("BottomSheetHelper.updateMap : ", "PrimaryCallTracker is null"); |
| return; |
| } |
| mCall = mPrimaryCallTracker.getPrimaryCall(); |
| LogUtil.i("BottomSheetHelper.updateMap","mCall = " + mCall); |
| |
| if (mCall != null && moreOptionsMap != null && mResources != null) { |
| maybeUpdateManageConferenceInMap(); |
| maybeUpdateAddParticipantInMap(); |
| maybeUpdateOneWayVideoOptionsInMap(); |
| maybeUpdateModifyCallInMap(); |
| maybeUpdateHideMeInMap(); |
| maybeUpdateDeflectInMap(); |
| maybeUpdateTransferInMap(); |
| maybeUpdateDialpadOptionInMap(); |
| maybeUpdateCancelModifyCallInMap(); |
| maybeUpdatePipModeInMap(); |
| maybeUpdateTirAcceptOptionsInMap(); |
| } |
| } |
| |
| // Utility function which converts options from string array to HashMap<String,Boolean> |
| private static ConcurrentHashMap<String,Boolean> prepareSheetOptions(String[][] answerOptArray) { |
| ConcurrentHashMap<String,Boolean> map = new ConcurrentHashMap<String,Boolean>(); |
| for (int iter = 0; iter < answerOptArray.length; iter ++) { |
| map.put(answerOptArray[iter][0],Boolean.valueOf(answerOptArray[iter][1])); |
| } |
| return map; |
| } |
| |
| private boolean isOneWayVideoOptionsVisible() { |
| final int primaryCallState = mCall.getState(); |
| final int requestedVideoState = mCall.getVideoTech().getRequestedVideoState(); |
| return (QtiCallUtils.useExt(mContext) && mCall.hasReceivedVideoUpgradeRequest() |
| && VideoProfile.isAudioOnly(mCall.getVideoState()) |
| && VideoProfile.isBidirectional(requestedVideoState)) |
| || ((DialerCallState.INCOMING == primaryCallState |
| || DialerCallState.CALL_WAITING == primaryCallState) |
| && (QtiCallUtils.isVideoBidirectional(mCall) |
| && QtiImsExtUtils.canAcceptAsOneWayVideo(getPhoneId(), mContext))); |
| } |
| |
| private boolean isModifyCallOptionsVisible() { |
| final int primaryCallState = mCall.getState(); |
| boolean hideModifyCallOption = false; |
| if (QtiImsExtUtils.isCancelModifyCallSupported(getPhoneId(), mContext)) { |
| hideModifyCallOption = !QtiCallUtils.hasReceiveVideoCapabilities(mCall) |
| && !QtiCallUtils.hasTransmitVideoCapabilities(mCall); |
| } |
| return QtiCallUtils.useExt(mContext) && (DialerCallState.ACTIVE == primaryCallState |
| || DialerCallState.ONHOLD == primaryCallState) |
| && QtiCallUtils.hasVoiceOrVideoCapabilities(mCall) |
| && !mCall.hasReceivedVideoUpgradeRequest() |
| && !isCancelModifyCallOptionsVisible() |
| && !hideModifyCallOption; |
| } |
| |
| private boolean isCancelModifyCallOptionsVisible() { |
| if (QtiImsExtUtils.isCancelModifyCallSupported(getPhoneId(), mContext)) { |
| DialerCall call = mPrimaryCallTracker.getPrimaryCall(); |
| return !mHasSentCancelUpgradeRequest && (call.getVideoTech().getSessionModificationState() |
| == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE); |
| } |
| return false; |
| } |
| |
| private void maybeUpdateManageConferenceInMap() { |
| /* show manage conference option only for active video conference calls if the call |
| has manage conference capability */ |
| boolean visible = mCall.isVideoCall() && mCall.getState() == DialerCallState.ACTIVE && |
| mCall.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE); |
| moreOptionsMap.put(mResources.getString(R.string.manageConferenceLabel), visible); |
| } |
| |
| private void maybeUpdatePipModeInMap() { |
| /* show Pip mode option only for active video calls if the settings db property |
| "disable_pip_mode" is set */ |
| if (!canDisablePipMode()) { |
| return; |
| } |
| final boolean visible = mCall.isVideoCall() && mCall.getState() == DialerCallState.ACTIVE |
| && !mCall.hasReceivedVideoUpgradeRequest(); |
| moreOptionsMap.put(mResources.getString(R.string.pipModeLabel), visible); |
| } |
| |
| public boolean isManageConferenceVisible() { |
| if (moreOptionsMap == null || mResources == null || mCall == null) { |
| LogUtil.w("isManageConferenceVisible","moreOptionsMap or mResources or mCall is null"); |
| return false; |
| } |
| |
| return moreOptionsMap.get(mResources.getString(R.string.manageConferenceLabel)).booleanValue() |
| && !mCall.hasReceivedVideoUpgradeRequest(); |
| } |
| |
| public void showBottomSheet(FragmentManager manager) { |
| LogUtil.d("BottomSheetHelper.showBottomSheet","moreOptionsMap: " + moreOptionsMap); |
| moreOptionsSheet = ExtBottomSheetFragment.newInstance(moreOptionsMap); |
| moreOptionsSheet.show(manager, null); |
| } |
| |
| public void dismissBottomSheet() { |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null || !inCallActivity.isVisible()) { |
| LogUtil.w("BottomSheetHelper.dismissBottomSheet", |
| "In call activity is either null or not visible"); |
| return; |
| } |
| if (moreOptionsSheet != null && moreOptionsSheet.isVisible()) { |
| moreOptionsSheet.dismiss(); |
| moreOptionsSheet = null; |
| } |
| if (callTransferDialog != null && callTransferDialog.isShowing()) { |
| callTransferDialog.dismiss(); |
| callTransferDialog = null; |
| } |
| if (modifyCallDialog != null && modifyCallDialog.isShowing()) { |
| modifyCallDialog.dismiss(); |
| } |
| |
| if (mCancelModifyCallDialog != null && mCancelModifyCallDialog.isShowing()) { |
| mCancelModifyCallDialog.dismiss(); |
| mCancelModifyCallDialog = null; |
| } |
| } |
| |
| public void optionSelected(@Nullable String text) { |
| //callback for bottomsheet clicks |
| LogUtil.d("BottomSheetHelper.optionSelected","text : " + text); |
| if (text.equals(mResources.getString(R.string.add_participant_option_msg))) { |
| if (QtiImsExtUtils.isCarrierConfigEnabled(getPhoneId(), mContext, |
| "add_multi_participants_enabled")) { |
| startAddMultiParticipantActivity(); |
| } else { |
| startAddParticipantActivity(); |
| } |
| } else if (text.equals(mResources.getString(R.string.manageConferenceLabel))) { |
| manageConferenceCall(); |
| } else if (text.equals(mResources.getString(R.string.qti_ims_hideMeText_unselected)) || |
| text.equals(mResources.getString(R.string.qti_ims_hideMeText_selected))) { |
| hideMeClicked(text.equals(mResources.getString(R.string.qti_ims_hideMeText_unselected))); |
| } else if (text.equals(mResources.getString(R.string.qti_description_target_deflect))) { |
| deflectCall(); |
| } else if (text.equals(mResources.getString(R.string.qti_description_transfer))) { |
| transferCall(); |
| } else if (text.equals(mResources.getString(R.string.dialpad_label))) { |
| showDialpad(); |
| } else if (text.equals(mResources.getString(R.string.video_tx_label))) { |
| acceptIncomingCallOrUpgradeRequest(VideoProfile.STATE_TX_ENABLED); |
| } else if (text.equals(mResources.getString(R.string.video_rx_label))) { |
| acceptIncomingCallOrUpgradeRequest(VideoProfile.STATE_RX_ENABLED); |
| } else if (text.equals(mResources.getString(R.string.modify_call_label))) { |
| displayModifyCallOptions(); |
| } else if (text.equals(mResources.getString(R.string.cancel_modify_call_label))) { |
| displayCancelModifyCallOptions(); |
| } else if (text.equals(mResources.getString(R.string.pipModeLabel))) { |
| VideoCallPresenter.showPipModeMenu(); |
| } else if (text.equals(mResources.getString(R.string.accept_call_with_tir_restricted_label))) { |
| acceptIncomingCallWithTir(QtiImsExtUtils.QTI_IMS_TIR_PRESENTATION_RESTRICTED); |
| } else if (text.equals(mResources.getString( |
| R.string.accept_call_with_tir_unrestricted_label))) { |
| acceptIncomingCallWithTir(QtiImsExtUtils.QTI_IMS_TIR_PRESENTATION_UNRESTRICTED); |
| } |
| moreOptionsSheet = null; |
| } |
| |
| public void sheetDismissed() { |
| LogUtil.d("BottomSheetHelper.sheetDismissed"," "); |
| moreOptionsSheet = null; |
| } |
| |
| private String[][] getMoreOptionsFromRes(final int resId) { |
| TypedArray typedArray = mResources.obtainTypedArray(resId); |
| String[][] array = new String[typedArray.length()][]; |
| for (int iter = 0;iter < typedArray.length(); iter++) { |
| int id = typedArray.getResourceId(iter, 0); |
| if (id > 0) { |
| array[iter] = mResources.getStringArray(id); |
| } |
| } |
| typedArray.recycle(); |
| return array; |
| } |
| |
| public boolean shallShowMoreButton(Activity activity) { |
| if (mPrimaryCallTracker != null) { |
| DialerCall call = mPrimaryCallTracker.getPrimaryCall(); |
| if (call != null && activity != null) { |
| int primaryCallState = call.getState(); |
| return !(activity.isInMultiWindowMode() |
| || call.isEmergencyCall() |
| || ((DialerCallState.isDialing(primaryCallState) || |
| DialerCallState.CONNECTING == primaryCallState) && |
| !call.isVideoCall()) |
| || DialerCallState.DISCONNECTING == primaryCallState |
| || call.hasSentVideoUpgradeRequest() |
| || !(getPhoneIdExtra(call) != QtiCallConstants.INVALID_PHONE_ID)) |
| || isCancelModifyCallOptionsVisible() |
| || canDisplayAcceptWithTirOptionsButtons(); |
| } |
| } |
| LogUtil.w("BottomSheetHelper shallShowMoreButton","returns false"); |
| return false; |
| } |
| |
| public void updateMoreButtonVisibility(boolean isVisible, View moreOptionsMenuButton) { |
| if (moreOptionsMenuButton == null) { |
| return; |
| } |
| if (isVisible) { |
| moreOptionsMenuButton.setVisibility(View.VISIBLE); |
| } else { |
| dismissBottomSheet(); |
| moreOptionsMenuButton.setVisibility(View.GONE); |
| } |
| } |
| |
| private boolean isHideMeOptionVisible() { |
| if (!QtiImsExtUtils.shallShowStaticImageUi(getPhoneId(), mContext) || |
| !VideoUtils.hasCameraPermissionAndShownPrivacyToast(mContext)) { |
| return false; |
| } |
| return mCall != null && mCall.isVideoCall() && mCall.getState() == DialerCallState.ACTIVE |
| && !mCall.hasReceivedVideoUpgradeRequest(); |
| } |
| |
| private void maybeUpdateHideMeInMap() { |
| LogUtil.v("BottomSheetHelper.maybeUpdateHideMeInMap", " mIsHideMe = " + mIsHideMe); |
| String hideMeText = mIsHideMe ? mResources.getString(R.string.qti_ims_hideMeText_selected) : |
| mResources.getString(R.string.qti_ims_hideMeText_unselected); |
| moreOptionsMap.put(hideMeText, isHideMeOptionVisible()); |
| } |
| |
| /** |
| * Handles click on hide me button |
| * @param isHideMe True if user selected hide me option else false |
| */ |
| private void hideMeClicked(boolean isHideMe) { |
| LogUtil.d("BottomSheetHelper.hideMeClicked", " isHideMe = " + isHideMe); |
| mIsHideMe = isHideMe; |
| if (isHideMe) { |
| // Replace "Hide Me" string with "Show Me" |
| moreOptionsMap.remove(mResources.getString(R.string.qti_ims_hideMeText_unselected)); |
| moreOptionsMap.put(mResources.getString(R.string.qti_ims_hideMeText_selected), isHideMe); |
| } else { |
| // Replace "Show Me" string with "Hide Me" |
| moreOptionsMap.remove(mResources.getString(R.string.qti_ims_hideMeText_selected)); |
| moreOptionsMap.put(mResources.getString(R.string.qti_ims_hideMeText_unselected), !isHideMe); |
| } |
| |
| /* Click on hideme shall change the static image state i.e. decision |
| is made in VideoCallPresenter whether to replace preview video with |
| static image or whether to resume preview video streaming */ |
| InCallPresenter.getInstance().notifyHideMeUiModeChanged(); |
| } |
| |
| // Returns TRUE if UE is in hide me mode else returns FALSE |
| public boolean isInHideMeMode(DialerCall call) { |
| LogUtil.v("BottomSheetHelper.isInHideMeMode", "mIsHideMe: " + mIsHideMe); |
| return mIsHideMe && |
| QtiImsExtUtils.shallShowStaticImageUi(getPhoneIdExtra(call), mContext); |
| } |
| |
| private int getPhoneIdExtra(DialerCall call) { |
| if (call == null) { |
| return QtiCallConstants.INVALID_PHONE_ID; |
| } |
| final Bundle extras = call.getExtras(); |
| return ((extras == null) ? QtiCallConstants.INVALID_PHONE_ID : |
| extras.getInt(QtiImsExtUtils.QTI_IMS_PHONE_ID_EXTRA_KEY, |
| QtiCallConstants.INVALID_PHONE_ID)); |
| } |
| |
| /** |
| * This API should be called only when there is a call. |
| * Caller should handle if INVALID_PHONE_ID is returned. |
| */ |
| public int getPhoneId() { |
| if (mPrimaryCallTracker == null) { |
| LogUtil.w("BottomSheetHelper.getPhoneId", "mPrimaryCallTracker is null."); |
| return QtiCallConstants.INVALID_PHONE_ID; |
| } |
| |
| final DialerCall call = mPrimaryCallTracker.getPrimaryCall(); |
| if (call == null) { |
| LogUtil.w("BottomSheetHelper.getPhoneId", "primaryCall is null."); |
| return QtiCallConstants.INVALID_PHONE_ID; |
| } |
| |
| final int phoneId = getPhoneIdExtra(call); |
| LogUtil.d("BottomSheetHelper.getPhoneId", "phoneId : " + phoneId); |
| return phoneId; |
| } |
| |
| @Override |
| public void onHideMeUiModeChanged() { |
| //No-op |
| } |
| |
| @Override |
| public void onOutgoingVideoSourceChanged(int videoSource) { |
| if (mCall == null) { |
| return; |
| } |
| if (videoSource == ScreenShareHelper.SCREEN && !mCall.isVideoCall()) { |
| changeToVideoClicked(mCall, VideoProfile.STATE_TX_ENABLED); |
| } |
| } |
| |
| @Override |
| public void onSessionModificationStateChange(DialerCall call) { |
| //No-op |
| } |
| |
| @Override |
| public void onSipDtmfChanged(int bitMask) { |
| //No-op |
| } |
| |
| @Override |
| public void onShowNextSecondaryCall(DialerCall nextSecondaryCall) { |
| //No-op |
| } |
| |
| @Override |
| public void onPrimaryCallChanged(DialerCall call) { |
| LogUtil.d("BottomSheetHelper.onPrimaryCallChanged", ""); |
| dismissBottomSheet(); |
| updateMap(); |
| } |
| |
| private void manageConferenceCall() { |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null) { |
| LogUtil.w("BottomSheetHelper.manageConferenceCall", "inCallActivity is null"); |
| return; |
| } |
| |
| inCallActivity.showConferenceFragment(true); |
| } |
| |
| private void maybeUpdateDeflectInMap() { |
| final boolean showDeflectCall = |
| mCall.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_DEFLECT) && |
| !mCall.isVideoCall() && !mCall.hasReceivedVideoUpgradeRequest(); |
| moreOptionsMap.put(mResources.getString(R.string.qti_description_target_deflect), |
| showDeflectCall); |
| } |
| |
| /** |
| * Deflect the incoming call. |
| */ |
| private void deflectCall() { |
| LogUtil.enterBlock("BottomSheetHelper.deflectCall"); |
| if(mCall == null ) { |
| LogUtil.w("BottomSheetHelper.deflectCall", "mCall is null"); |
| return; |
| } |
| String deflectCallNumber = QtiImsExtUtils.getCallDeflectNumber( |
| mContext.getContentResolver()); |
| /* If not set properly, inform via Log */ |
| if (deflectCallNumber == null) { |
| LogUtil.w("BottomSheetHelper.deflectCall", |
| "Number not set. Provide the number via IMS settings and retry."); |
| return; |
| } |
| Uri deflectCallNumberUri = CallUtil.getCallUri(deflectCallNumber); |
| if (deflectCallNumberUri == null) { |
| LogUtil.w("BottomSheetHelper.deflectCall", "Deflect number Uri is null."); |
| return; |
| } |
| |
| LogUtil.d("BottomSheetHelper.deflectCall", "mCall:" + mCall + |
| "deflectCallNumberUri: " + Log.pii(deflectCallNumberUri)); |
| mCall.deflectCall(deflectCallNumberUri); |
| } |
| |
| private void maybeUpdateTransferInMap() { |
| final boolean showTransferOptions = |
| (mCall.can(android.telecom.Call.Details.CAPABILITY_TRANSFER) || |
| mCall.can(android.telecom.Call.Details.CAPABILITY_TRANSFER_CONSULTATIVE)) && |
| !mCall.hasReceivedVideoUpgradeRequest(); |
| LogUtil.i("BottomSheetHelper.maybeUpdateTransferInMap", |
| "value of showTransferOptions in BottomSheetHelper = " + showTransferOptions); |
| moreOptionsMap.put(mResources.getString(R.string.qti_description_transfer), |
| showTransferOptions); |
| } |
| |
| private void transferCall() { |
| LogUtil.enterBlock("BottomSheetHelper.transferCall"); |
| if(mCall == null ) { |
| LogUtil.w("BottomSheetHelper.transferCall", "mCall is null"); |
| return; |
| } |
| displayCallTransferOptions(); |
| } |
| |
| /** |
| * The function is called when Call Transfer button gets pressed. The function creates and |
| * displays call transfer options. |
| */ |
| private void displayCallTransferOptions() { |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null) { |
| LogUtil.e("BottomSheetHelper.displayCallTransferOptions", "inCallActivity is NULL"); |
| return; |
| } |
| final ArrayList<CharSequence> items = getCallTransferOptions(); |
| AlertDialog.Builder builder = new AlertDialog.Builder(inCallActivity) |
| .setTitle(R.string.qti_description_transfer); |
| |
| DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int item) { |
| LogUtil.d("BottomSheetHelper.onCallTransferItemClicked", "" + items.get(item)); |
| onCallTransferItemClicked(item); |
| dialog.dismiss(); |
| } |
| }; |
| builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), INVALID_INDEX, listener); |
| callTransferDialog = builder.create(); |
| callTransferDialog.show(); |
| } |
| |
| private ArrayList<CharSequence> getCallTransferOptions() { |
| final ArrayList<CharSequence> items = new ArrayList<CharSequence>(); |
| if (mCall.can(android.telecom.Call.Details.CAPABILITY_TRANSFER_CONSULTATIVE) |
| && isConsultativeTransferOnSameSub()) { |
| items.add(mResources.getText(R.string.qti_ims_onscreenBlindTransfer)); |
| items.add(mResources.getText(R.string.qti_ims_onscreenAssuredTransfer)); |
| items.add(mResources.getText(R.string.qti_ims_onscreenConsultativeTransfer)); |
| } else if (mCall.can(android.telecom.Call.Details.CAPABILITY_TRANSFER)) { |
| items.add(mResources.getText(R.string.qti_ims_onscreenBlindTransfer)); |
| items.add(mResources.getText(R.string.qti_ims_onscreenAssuredTransfer)); |
| } |
| return items; |
| } |
| |
| /** |
| * Returns true if active and secondary banner call are on the same sub, false otherwise. |
| */ |
| private static boolean isConsultativeTransferOnSameSub() { |
| DialerCall secondaryCall = InCallPresenter.getInstance().getSecondaryCall(); |
| DialerCall foregroundCall = CallList.getInstance().getActiveCall(); |
| return secondaryCall != null && foregroundCall != null && |
| Objects.equals(secondaryCall.getAccountHandle(), foregroundCall.getAccountHandle()); |
| } |
| |
| private void onCallTransferItemClicked(int item) { |
| switch(item) { |
| case BLIND_TRANSFER: |
| transferCall(false); |
| break; |
| case ASSURED_TRANSFER: |
| transferCall(true); |
| break; |
| case CONSULTATIVE_TRANSFER: |
| transferCallConsultative(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| //Called for blind and assured transfer |
| private void transferCall(boolean isConfirmationRequired) { |
| LogUtil.enterBlock("BottomSheetHelper.transferCall"); |
| if(mCall == null ) { |
| LogUtil.w("BottomSheetHelper.transferCall", "mCall is null"); |
| return; |
| } |
| String transferCallNumber = QtiImsExtUtils.getCallDeflectNumber( |
| mContext.getContentResolver()); |
| /* If not set properly, inform via Log */ |
| if (transferCallNumber == null) { |
| LogUtil.w("BottomSheetHelper.transferCall","transfer number error, number is null"); |
| return; |
| } |
| Uri transferCallNumberUri = CallUtil.getCallUri(transferCallNumber); |
| if (transferCallNumberUri == null) { |
| LogUtil.w("BottomSheetHelper.transferCall", "transfer number Uri is null."); |
| return; |
| } |
| LogUtil.d("BottomSheetHelper.transferCall", "mCall:" + mCall + |
| "transferCallNumberUri: " + Log.pii(transferCallNumberUri)); |
| mCall.transferCall(transferCallNumberUri, isConfirmationRequired); |
| } |
| |
| //Called for consultative transfer |
| private void transferCallConsultative() { |
| LogUtil.enterBlock("BottomSheetHelper.transferCallConsultative"); |
| if (mCall == null) { |
| LogUtil.w("BottomSheetHelper.transferCallConsultative", "mCall is null"); |
| return; |
| } |
| //For Consultative transfer number is not needed |
| DialerCall backgroundCall = CallList.getInstance().getBackgroundCall(); |
| if (backgroundCall == null || |
| !Objects.equals(backgroundCall.getAccountHandle(), mCall.getAccountHandle())) { |
| LogUtil.w("BottomSheetHelper.transferCallConsultative", "backgroundCall is null" + |
| " or background call is on a different sub."); |
| return; |
| } |
| mCall.transferCall(backgroundCall); |
| } |
| |
| private void showDialpad() { |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null) { |
| LogUtil.w("BottomSheetHelper.showDialpad", "inCallActivity is null"); |
| return; |
| } |
| |
| inCallActivity.showDialpadFragment(true, true); |
| } |
| |
| private void maybeUpdateDialpadOptionInMap() { |
| // Enable dialpad option in bottomsheet for video calls or video CRS. |
| // When video call is held, UI displays onscreen dialpad button |
| // similar to volte calls. |
| final int primaryCallState = mCall.getNonConferenceState(); |
| final boolean enable = (mCall.isVideoCall() |
| && primaryCallState != DialerCallState.INCOMING |
| && primaryCallState != DialerCallState.CALL_WAITING |
| && primaryCallState != DialerCallState.ONHOLD) |
| || QtiCallUtils.isVideoCrs(mCall); |
| moreOptionsMap.put(mResources.getString(R.string.dialpad_label), enable); |
| } |
| |
| private boolean isAddParticipantSupported() { |
| boolean showAddParticipant = mCall != null |
| && mCall.can(android.telecom.Call.Details.CAPABILITY_ADD_PARTICIPANT) |
| && UserManagerCompat.isUserUnlocked(mContext) |
| && !mCall.hasReceivedVideoUpgradeRequest(); |
| if (QtiImsExtUtils.isCarrierConfigEnabled(getPhoneId(), mContext, |
| "add_participant_only_in_conference")) { |
| showAddParticipant = showAddParticipant && (mCall != null) && (mCall.isConferenceCall()); |
| } |
| return showAddParticipant; |
| } |
| |
| private void startAddMultiParticipantActivity() { |
| Intent intent = QtiCallUtils.getAddParticipantsIntent(null); |
| List<String> childCallIdList = (mCall != null) ? mCall.getChildCallIds() : null; |
| if (childCallIdList != null) { |
| StringBuffer sb = new StringBuffer(); |
| for (String tmp: childCallIdList) { |
| String number = CallList.getInstance().getCallById(tmp).getNumber(); |
| if (number.contains(";")) { |
| String[] temp = number.split(";"); |
| number = temp[0]; |
| } |
| sb.append(number).append(";"); |
| } |
| intent.putExtra("current_participant_list", sb.toString()); |
| } else { |
| LogUtil.e("BottomSheetHelper.startAddMultiParticipantActivity", |
| "sendAddMultiParticipantsIntent, childCallIdList null."); |
| } |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null || !inCallActivity.isVisible()) { |
| LogUtil.w("BottomSheetHelper.startAddMultiParticipantActivity", |
| "In call activity is either null or not visible"); |
| return; |
| } |
| try { |
| inCallActivity.startActivityForResult(intent, |
| QtiCallUtils.REQUEST_ADD_PARTICIPANT); |
| } catch (ActivityNotFoundException e) { |
| LogUtil.e("BottomSheetHelper.startAddMultiParticipantActivity", |
| "Activity not found. Exception = " + e); |
| } |
| } |
| |
| private void maybeUpdateAddParticipantInMap() { |
| moreOptionsMap.put(mContext.getResources().getString(R.string.add_participant_option_msg), |
| isAddParticipantSupported()); |
| } |
| |
| private void startAddParticipantActivity() { |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null || !inCallActivity.isVisible()) { |
| LogUtil.w("BottomSheetHelper.startAddParticipantActivity", |
| "In call activity is either null or not visible"); |
| return; |
| } |
| try { |
| inCallActivity.startActivityForResult(QtiCallUtils.getAddParticipantsIntent(), |
| QtiCallUtils.REQUEST_ADD_PARTICIPANT); |
| } catch (ActivityNotFoundException e) { |
| LogUtil.e("BottomSheetHelper.startAddParticipantActivity", |
| "Activity not found. Exception = " + e); |
| } |
| } |
| |
| private void maybeUpdateOneWayVideoOptionsInMap() { |
| final boolean showOneWayVideo = isOneWayVideoOptionsVisible(); |
| moreOptionsMap.put(mResources.getString(R.string.video_rx_label), showOneWayVideo); |
| moreOptionsMap.put(mResources.getString(R.string.video_tx_label), showOneWayVideo); |
| } |
| |
| private void maybeUpdateModifyCallInMap() { |
| moreOptionsMap.put(mContext.getResources().getString(R.string.modify_call_label), |
| isModifyCallOptionsVisible()); |
| } |
| |
| private void maybeUpdateCancelModifyCallInMap() { |
| moreOptionsMap.put(mContext.getResources().getString(R.string.cancel_modify_call_label), |
| isCancelModifyCallOptionsVisible()); |
| } |
| |
| private void acceptIncomingCallOrUpgradeRequest(int videoState) { |
| if (mCall == null) { |
| LogUtil.e("BottomSheetHelper.acceptIncomingCallOrUpgradeRequest", "Call is null. Return"); |
| return; |
| } |
| |
| if (mCall.answeringDisconnectsOtherCall()) { |
| AnswerUtils.disconnectAllAndAnswer(videoState); |
| return; |
| } |
| |
| if (mCall.hasReceivedVideoUpgradeRequest()) { |
| mCall.getVideoTech().acceptVideoRequest(videoState); |
| } else { |
| mCall.answer(videoState); |
| } |
| } |
| |
| /** |
| * The function is called when Modify Call button gets pressed. The function creates and |
| * displays modify call options. |
| */ |
| public void displayModifyCallOptions() { |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null) { |
| LogUtil.e("BottomSheetHelper.displayModifyCallOptions", "inCallActivity is NULL"); |
| return; |
| } |
| |
| if (mCall == null) { |
| LogUtil.e("BottomSheetHelper.displayModifyCallOptions", |
| "Can't display modify call options. Call is null"); |
| return; |
| } |
| |
| boolean isVideoEnabled = CallUtil.isVideoEnabled(mContext); |
| final ArrayList<CharSequence> items = new ArrayList<CharSequence>(); |
| final ArrayList<Integer> itemToCallType = new ArrayList<Integer>(); |
| |
| // Prepare the string array and mapping. |
| if (QtiCallUtils.hasVoiceCapabilities(mCall) && mCall.isVideoCall()) { |
| items.add(mResources.getText(R.string.modify_call_option_voice)); |
| itemToCallType.add(VideoProfile.STATE_AUDIO_ONLY); |
| } |
| |
| if (isVideoEnabled && QtiCallUtils.hasReceiveVideoCapabilities(mCall) |
| && !QtiCallUtils.isVideoRxOnly(mCall)) { |
| items.add(mResources.getText(R.string.modify_call_option_vt_rx)); |
| itemToCallType.add(VideoProfile.STATE_RX_ENABLED); |
| } |
| |
| if (isVideoEnabled && QtiCallUtils.hasTransmitVideoCapabilities(mCall) |
| && (!QtiCallUtils.isVideoTxOnly(mCall) |
| || ScreenShareHelper.screenShareRequested())) { |
| items.add(mResources.getText(R.string.modify_call_option_vt_tx)); |
| itemToCallType.add(VideoProfile.STATE_TX_ENABLED); |
| } |
| |
| if (isVideoEnabled && QtiCallUtils.hasReceiveVideoCapabilities(mCall) |
| && QtiCallUtils.hasTransmitVideoCapabilities(mCall) |
| && (!QtiCallUtils.isVideoBidirectional(mCall) |
| || ScreenShareHelper.screenShareRequested())) { |
| items.add(mResources.getText(R.string.modify_call_option_vt)); |
| itemToCallType.add(VideoProfile.STATE_BIDIRECTIONAL); |
| } |
| |
| if (isVideoEnabled && canDisplayScreenShareButton() && |
| mCall.getState() == DialerCallState.ACTIVE && |
| QtiCallUtils.hasTransmitVideoCapabilities(mCall) |
| && !ScreenShareHelper.screenShareRequested() |
| && !QtiCallUtils.isVideoRxOnly(mCall)) { |
| items.add(mResources.getText(R.string.modify_call_option_screen_share)); |
| itemToCallType.add(ScreenShareHelper.VIDEO_SCREEN_SHARE); |
| } |
| |
| AlertDialog.Builder builder = new AlertDialog.Builder(inCallActivity); |
| builder.setTitle(R.string.modify_call_option_title); |
| |
| DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int item) { |
| final int setCallType = itemToCallType.get(item); |
| if (setCallType == ScreenShareHelper.VIDEO_SCREEN_SHARE) { |
| ScreenShareHelper.requestScreenSharePermission(); |
| dialog.dismiss(); |
| return; |
| } |
| |
| if (ScreenShareHelper.screenShareRequested()) { |
| if (mCall.getVideoState() == setCallType) { |
| InCallPresenter.getInstance().notifyOutgoingVideoSourceChanged( |
| ScreenShareHelper.CAMERA); |
| dialog.dismiss(); |
| return; |
| } |
| if (setCallType != VideoProfile.STATE_AUDIO_ONLY |
| && setCallType != VideoProfile.STATE_RX_ENABLED) { |
| InCallPresenter.getInstance().notifyOutgoingVideoSourceChanged( |
| ScreenShareHelper.CAMERA); |
| } else if (setCallType == VideoProfile.STATE_RX_ENABLED || |
| setCallType == VideoProfile.STATE_AUDIO_ONLY) { |
| InCallPresenter.getInstance().notifyOutgoingVideoSourceChanged( |
| ScreenShareHelper.NONE); |
| } |
| } |
| |
| Log.v(this, "Videocall: ModifyCall: upgrade/downgrade to " |
| + QtiCallUtils.callTypeToString(setCallType)); |
| changeToVideoClicked(mCall, setCallType); |
| dialog.dismiss(); |
| } |
| }; |
| builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), INVALID_INDEX, listener); |
| modifyCallDialog = builder.create(); |
| modifyCallDialog.show(); |
| } |
| |
| public void displayCancelModifyCallOptions() { |
| final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity(); |
| if (inCallActivity == null) { |
| LogUtil.e("BottomSheetHelper.displayCancelModifyCallOptions", "inCallActivity is NULL"); |
| return; |
| } |
| AlertDialog.Builder alertDialog = new AlertDialog.Builder(inCallActivity); |
| alertDialog.setTitle(R.string.cancel_modify_call_title); |
| alertDialog.setPositiveButton(R.string.cancel_upgrade, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| cancelUpgradeClicked(mCall); |
| } |
| } ); |
| alertDialog.setNegativeButton(R.string.not_cancel, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| Log.d(this, "not cancel voice call upgrade to video"); |
| } |
| } ); |
| mCancelModifyCallDialog = alertDialog.create(); |
| mCancelModifyCallDialog.show(); |
| } |
| |
| /** |
| * Sends a session modify request to the telephony framework |
| */ |
| private void changeToVideoClicked(DialerCall call, int videoState) { |
| call.getVideoTech().upgradeToVideo(videoState); |
| } |
| |
| public PrimaryCallTracker getPrimaryCallTracker() { |
| return mPrimaryCallTracker; |
| } |
| |
| /** |
| * Cancel the upgrade request. |
| */ |
| private void cancelUpgradeClicked(DialerCall call) { |
| LogUtil.enterBlock("BottomSheetHelper.cancelUpgradeClicked"); |
| if(call == null ) { |
| LogUtil.w("BottomSheetHelper.cancelUpgradeClicked", "call is null"); |
| return; |
| } |
| |
| if (mQtiImsExtManager == null) { |
| QtiCallUtils.displayToast(mContext, "An error occurred, Please try again later."); |
| return; |
| } |
| |
| try { |
| LogUtil.d("BottomSheetHelper.cancelUpgradeClicked", |
| "Sending cancel upgrade request with Phone id " + getPhoneId()); |
| mQtiImsExtManager.sendCancelModifyCall(getPhoneId(),imsInterfaceListener); |
| mHasSentCancelUpgradeRequest = true; |
| maybeUpdateCancelModifyCallInMap(); |
| } catch (QtiImsException e) { |
| LogUtil.e("BottomSheetHelper.cancelUpgradeClicked", "sendCancelModifyCall exception " + e); |
| } |
| } |
| |
| /** |
| * Handles a change to the fullscreen mode of the app. |
| * |
| * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise. |
| */ |
| @Override |
| public void onFullscreenModeChanged(boolean isFullscreenMode) { |
| if (isFullscreenMode) { |
| dismissBottomSheet(); |
| } |
| } |
| |
| public boolean canDisablePipMode() { |
| return (Settings.Global.getInt( |
| mContext.getContentResolver(), "disable_pip_mode", 0) != 0); |
| } |
| |
| private boolean canDisplayScreenShareButton() { |
| return Settings.Global.getInt(mContext.getContentResolver(), |
| "enable_screen_share", 0) == 1; |
| } |
| |
| private boolean canDisplayAcceptWithTirOptionsButtons() { |
| boolean showAcceptWithTirOptions = false; |
| if (mCall == null) { |
| LogUtil.d("BottomSheetHelper.canDisplayAcceptWithTirOptionsButtons", |
| "mCall is null"); |
| return false; |
| } |
| final int primaryCallState = mCall.getState(); |
| if (primaryCallState == DialerCallState.INCOMING || |
| primaryCallState == DialerCallState.CALL_WAITING) { |
| Bundle extras = mCall.getExtras(); |
| showAcceptWithTirOptions = (extras == null) ? false : |
| extras.getBoolean(QtiImsExtUtils.EXTRA_TIR_OVERWRITE_ALLOWED, false); |
| } |
| return showAcceptWithTirOptions; |
| } |
| |
| private void maybeUpdateTirAcceptOptionsInMap() { |
| boolean visible = canDisplayAcceptWithTirOptionsButtons(); |
| moreOptionsMap.put(mResources.getString( |
| R.string.accept_call_with_tir_restricted_label), visible); |
| moreOptionsMap.put(mResources.getString( |
| R.string.accept_call_with_tir_unrestricted_label), visible); |
| } |
| |
| private void acceptIncomingCallWithTir(int presentation) { |
| LogUtil.enterBlock("BottomSheetHelper.acceptIncomingCallWithTir"); |
| if (mCall == null) { |
| LogUtil.d("BottomSheetHelper.acceptIncomingCallWithTir", "mCall is null"); |
| return; |
| } |
| try { |
| Bundle extra = new Bundle(); |
| extra.putInt(QtiImsExtUtils.EXTRA_ANSWER_OPTION_TIR_CONFIG, |
| presentation); |
| mQtiImsExtManager.setAnswerExtras(getPhoneId(), extra); |
| acceptIncomingCallOrUpgradeRequest(mCall.getVideoState()); |
| } catch (QtiImsException e) { |
| LogUtil.e("BottomSheetHelper.acceptIncomingCallWithTir", |
| "setAnswerExtras exception " + e); |
| } |
| } |
| |
| public QtiImsExtManager getQtiImsExtManager() { |
| return mQtiImsExtManager; |
| } |
| } |