| /* |
| * 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.call; |
| |
| import android.content.Context; |
| import android.hardware.camera2.CameraCharacteristics; |
| import android.net.Uri; |
| import android.os.Build.VERSION; |
| import android.os.Build.VERSION_CODES; |
| import android.os.Bundle; |
| import android.os.Trace; |
| import android.support.annotation.IntDef; |
| import android.support.annotation.Nullable; |
| import android.telecom.Call; |
| import android.telecom.Call.Details; |
| import android.telecom.Connection; |
| import android.telecom.DisconnectCause; |
| import android.telecom.GatewayInfo; |
| import android.telecom.InCallService.VideoCall; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.StatusHints; |
| import android.telecom.TelecomManager; |
| import android.telecom.VideoProfile; |
| import android.telephony.PhoneNumberUtils; |
| import android.text.TextUtils; |
| import com.android.contacts.common.compat.CallCompat; |
| import com.android.contacts.common.compat.TelephonyManagerCompat; |
| import com.android.contacts.common.compat.telecom.TelecomManagerCompat; |
| import com.android.dialer.callintent.CallIntentParser; |
| import com.android.dialer.callintent.nano.CallInitiationType; |
| import com.android.dialer.callintent.nano.CallSpecificAppData; |
| import com.android.dialer.common.Assert; |
| import com.android.dialer.common.ConfigProviderBindings; |
| import com.android.dialer.common.LogUtil; |
| import com.android.dialer.logging.nano.ContactLookupResult; |
| import com.android.dialer.util.CallUtil; |
| import com.android.incallui.latencyreport.LatencyReport; |
| import com.android.incallui.util.TelecomCallUtil; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Objects; |
| import java.util.UUID; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.TimeUnit; |
| |
| /** Describes a single call and its state. */ |
| public class DialerCall { |
| |
| public static final int CALL_HISTORY_STATUS_UNKNOWN = 0; |
| public static final int CALL_HISTORY_STATUS_PRESENT = 1; |
| public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2; |
| private static final String ID_PREFIX = "DialerCall_"; |
| private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS = |
| "emergency_callback_window_millis"; |
| private static int sIdCounter = 0; |
| |
| /** |
| * The unique call ID for every call. This will help us to identify each call and allow us the |
| * ability to stitch impressions to calls if needed. |
| */ |
| private final String uniqueCallId = UUID.randomUUID().toString(); |
| |
| private final Call mTelecomCall; |
| private final LatencyReport mLatencyReport; |
| private final String mId; |
| private final List<String> mChildCallIds = new ArrayList<>(); |
| private final VideoSettings mVideoSettings = new VideoSettings(); |
| private final LogState mLogState = new LogState(); |
| private final Context mContext; |
| private final DialerCallDelegate mDialerCallDelegate; |
| private final List<DialerCallListener> mListeners = new CopyOnWriteArrayList<>(); |
| private final List<CannedTextResponsesLoadedListener> mCannedTextResponsesLoadedListeners = |
| new CopyOnWriteArrayList<>(); |
| |
| private boolean mIsEmergencyCall; |
| private Uri mHandle; |
| private int mState = State.INVALID; |
| private DisconnectCause mDisconnectCause; |
| |
| private boolean hasShownWiFiToLteHandoverToast; |
| private boolean doNotShowDialogForHandoffToWifiFailure; |
| |
| @SessionModificationState private int mSessionModificationState; |
| private int mVideoState; |
| /** mRequestedVideoState is used to store requested upgrade / downgrade video state */ |
| private int mRequestedVideoState = VideoProfile.STATE_AUDIO_ONLY; |
| |
| private InCallVideoCallCallback mVideoCallCallback; |
| private boolean mIsVideoCallCallbackRegistered; |
| private String mChildNumber; |
| private String mLastForwardedNumber; |
| private String mCallSubject; |
| private PhoneAccountHandle mPhoneAccountHandle; |
| @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN; |
| private boolean mIsSpam; |
| private boolean mIsBlocked; |
| private boolean isInUserSpamList; |
| private boolean isInUserWhiteList; |
| private boolean isInGlobalSpamList; |
| private boolean didShowCameraPermission; |
| private String callProviderLabel; |
| private String callbackNumber; |
| |
| public static String getNumberFromHandle(Uri handle) { |
| return handle == null ? "" : handle.getSchemeSpecificPart(); |
| } |
| |
| /** |
| * Whether the call is put on hold by remote party. This is different than the {@link |
| * State.ONHOLD} state which indicates that the call is being held locally on the device. |
| */ |
| private boolean isRemotelyHeld; |
| |
| /** |
| * Indicates whether the phone account associated with this call supports specifying a call |
| * subject. |
| */ |
| private boolean mIsCallSubjectSupported; |
| |
| private final Call.Callback mTelecomCallCallback = |
| new Call.Callback() { |
| @Override |
| public void onStateChanged(Call call, int newState) { |
| LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState); |
| update(); |
| } |
| |
| @Override |
| public void onParentChanged(Call call, Call newParent) { |
| LogUtil.v( |
| "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent); |
| update(); |
| } |
| |
| @Override |
| public void onChildrenChanged(Call call, List<Call> children) { |
| update(); |
| } |
| |
| @Override |
| public void onDetailsChanged(Call call, Call.Details details) { |
| LogUtil.v("TelecomCallCallback.onStateChanged", " call=" + call + " details=" + details); |
| update(); |
| } |
| |
| @Override |
| public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) { |
| LogUtil.v( |
| "TelecomCallCallback.onStateChanged", |
| "call=" + call + " cannedTextResponses=" + cannedTextResponses); |
| for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) { |
| listener.onCannedTextResponsesLoaded(DialerCall.this); |
| } |
| } |
| |
| @Override |
| public void onPostDialWait(Call call, String remainingPostDialSequence) { |
| LogUtil.v( |
| "TelecomCallCallback.onStateChanged", |
| "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence); |
| update(); |
| } |
| |
| @Override |
| public void onVideoCallChanged(Call call, VideoCall videoCall) { |
| LogUtil.v( |
| "TelecomCallCallback.onStateChanged", "call=" + call + " videoCall=" + videoCall); |
| update(); |
| } |
| |
| @Override |
| public void onCallDestroyed(Call call) { |
| LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call); |
| call.unregisterCallback(this); |
| } |
| |
| @Override |
| public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) { |
| LogUtil.v( |
| "DialerCall.onConferenceableCallsChanged", |
| "call %s, conferenceable calls: %d", |
| call, |
| conferenceableCalls.size()); |
| update(); |
| } |
| |
| @Override |
| public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) { |
| LogUtil.v( |
| "DialerCall.onConnectionEvent", |
| "Call: " + call + ", Event: " + event + ", Extras: " + extras); |
| switch (event) { |
| // The Previous attempt to Merge two calls together has failed in Telecom. We must |
| // now update the UI to possibly re-enable the Merge button based on the number of |
| // currently conferenceable calls available or Connection Capabilities. |
| case android.telecom.Connection.EVENT_CALL_MERGE_FAILED: |
| update(); |
| break; |
| case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE: |
| notifyWiFiToLteHandover(); |
| break; |
| case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED: |
| notifyHandoverToWifiFailed(); |
| break; |
| case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD: |
| isRemotelyHeld = true; |
| update(); |
| break; |
| case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD: |
| isRemotelyHeld = false; |
| update(); |
| break; |
| default: |
| break; |
| } |
| } |
| }; |
| private long mTimeAddedMs; |
| |
| public DialerCall( |
| Context context, |
| DialerCallDelegate dialerCallDelegate, |
| Call telecomCall, |
| LatencyReport latencyReport, |
| boolean registerCallback) { |
| Assert.isNotNull(context); |
| mContext = context; |
| mDialerCallDelegate = dialerCallDelegate; |
| mTelecomCall = telecomCall; |
| mLatencyReport = latencyReport; |
| mId = ID_PREFIX + Integer.toString(sIdCounter++); |
| |
| updateFromTelecomCall(registerCallback); |
| |
| if (registerCallback) { |
| mTelecomCall.registerCallback(mTelecomCallCallback); |
| } |
| |
| mTimeAddedMs = System.currentTimeMillis(); |
| parseCallSpecificAppData(); |
| } |
| |
| private static int translateState(int state) { |
| switch (state) { |
| case Call.STATE_NEW: |
| case Call.STATE_CONNECTING: |
| return DialerCall.State.CONNECTING; |
| case Call.STATE_SELECT_PHONE_ACCOUNT: |
| return DialerCall.State.SELECT_PHONE_ACCOUNT; |
| case Call.STATE_DIALING: |
| return DialerCall.State.DIALING; |
| case Call.STATE_PULLING_CALL: |
| return DialerCall.State.PULLING; |
| case Call.STATE_RINGING: |
| return DialerCall.State.INCOMING; |
| case Call.STATE_ACTIVE: |
| return DialerCall.State.ACTIVE; |
| case Call.STATE_HOLDING: |
| return DialerCall.State.ONHOLD; |
| case Call.STATE_DISCONNECTED: |
| return DialerCall.State.DISCONNECTED; |
| case Call.STATE_DISCONNECTING: |
| return DialerCall.State.DISCONNECTING; |
| default: |
| return DialerCall.State.INVALID; |
| } |
| } |
| |
| public static boolean areSame(DialerCall call1, DialerCall call2) { |
| if (call1 == null && call2 == null) { |
| return true; |
| } else if (call1 == null || call2 == null) { |
| return false; |
| } |
| |
| // otherwise compare call Ids |
| return call1.getId().equals(call2.getId()); |
| } |
| |
| public static boolean areSameNumber(DialerCall call1, DialerCall call2) { |
| if (call1 == null && call2 == null) { |
| return true; |
| } else if (call1 == null || call2 == null) { |
| return false; |
| } |
| |
| // otherwise compare call Numbers |
| return TextUtils.equals(call1.getNumber(), call2.getNumber()); |
| } |
| |
| public void addListener(DialerCallListener listener) { |
| Assert.isMainThread(); |
| mListeners.add(listener); |
| } |
| |
| public void removeListener(DialerCallListener listener) { |
| Assert.isMainThread(); |
| mListeners.remove(listener); |
| } |
| |
| public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) { |
| Assert.isMainThread(); |
| mCannedTextResponsesLoadedListeners.add(listener); |
| } |
| |
| public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) { |
| Assert.isMainThread(); |
| mCannedTextResponsesLoadedListeners.remove(listener); |
| } |
| |
| public void notifyWiFiToLteHandover() { |
| LogUtil.i("DialerCall.notifyWiFiToLteHandover", ""); |
| for (DialerCallListener listener : mListeners) { |
| listener.onWiFiToLteHandover(); |
| } |
| } |
| |
| public void notifyHandoverToWifiFailed() { |
| LogUtil.i("DialerCall.notifyHandoverToWifiFailed", ""); |
| for (DialerCallListener listener : mListeners) { |
| listener.onHandoverToWifiFailure(); |
| } |
| } |
| |
| /* package-private */ Call getTelecomCall() { |
| return mTelecomCall; |
| } |
| |
| public StatusHints getStatusHints() { |
| return mTelecomCall.getDetails().getStatusHints(); |
| } |
| |
| /** |
| * @return video settings of the call, null if the call is not a video call. |
| * @see VideoProfile |
| */ |
| public VideoSettings getVideoSettings() { |
| return mVideoSettings; |
| } |
| |
| private void update() { |
| Trace.beginSection("Update"); |
| int oldState = getState(); |
| // We want to potentially register a video call callback here. |
| updateFromTelecomCall(true /* registerCallback */); |
| if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) { |
| for (DialerCallListener listener : mListeners) { |
| listener.onDialerCallDisconnect(); |
| } |
| } else { |
| for (DialerCallListener listener : mListeners) { |
| listener.onDialerCallUpdate(); |
| } |
| } |
| Trace.endSection(); |
| } |
| |
| private void updateFromTelecomCall(boolean registerCallback) { |
| LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString()); |
| final int translatedState = translateState(mTelecomCall.getState()); |
| if (mState != State.BLOCKED) { |
| setState(translatedState); |
| setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause()); |
| maybeCancelVideoUpgrade(mTelecomCall.getDetails().getVideoState()); |
| } |
| |
| if (registerCallback && mTelecomCall.getVideoCall() != null) { |
| if (mVideoCallCallback == null) { |
| mVideoCallCallback = new InCallVideoCallCallback(this); |
| } |
| mTelecomCall.getVideoCall().registerCallback(mVideoCallCallback); |
| mIsVideoCallCallbackRegistered = true; |
| } |
| |
| mChildCallIds.clear(); |
| final int numChildCalls = mTelecomCall.getChildren().size(); |
| for (int i = 0; i < numChildCalls; i++) { |
| mChildCallIds.add( |
| mDialerCallDelegate |
| .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i)) |
| .getId()); |
| } |
| |
| // The number of conferenced calls can change over the course of the call, so use the |
| // maximum number of conferenced child calls as the metric for conference call usage. |
| mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls); |
| |
| updateFromCallExtras(mTelecomCall.getDetails().getExtras()); |
| |
| // If the handle of the call has changed, update state for the call determining if it is an |
| // emergency call. |
| Uri newHandle = mTelecomCall.getDetails().getHandle(); |
| if (!Objects.equals(mHandle, newHandle)) { |
| mHandle = newHandle; |
| updateEmergencyCallState(); |
| } |
| |
| // If the phone account handle of the call is set, cache capability bit indicating whether |
| // the phone account supports call subjects. |
| PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle(); |
| if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) { |
| mPhoneAccountHandle = newPhoneAccountHandle; |
| |
| if (mPhoneAccountHandle != null) { |
| PhoneAccount phoneAccount = |
| mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle); |
| if (phoneAccount != null) { |
| mIsCallSubjectSupported = |
| phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT); |
| } |
| } |
| } |
| |
| if (mSessionModificationState |
| == DialerCall.SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE |
| && isVideoCall()) { |
| // We find out in {@link InCallVideoCallCallback.onSessionModifyResponseReceived} |
| // whether the video upgrade request was accepted. We don't clear the session modification |
| // state right away though to avoid having the UI switch from video to voice to video. |
| // Once the underlying telecom call updates to video mode it's safe to clear the state. |
| LogUtil.i( |
| "DialerCall.updateFromTelecomCall", |
| "upgraded to video, clearing session modification state"); |
| setSessionModificationState(DialerCall.SESSION_MODIFICATION_STATE_NO_REQUEST); |
| } |
| } |
| |
| /** |
| * Tests corruption of the {@code callExtras} bundle by calling {@link |
| * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will |
| * be thrown and caught by this function. |
| * |
| * @param callExtras the bundle to verify |
| * @return {@code true} if the bundle is corrupted, {@code false} otherwise. |
| */ |
| protected boolean areCallExtrasCorrupted(Bundle callExtras) { |
| /** |
| * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras |
| * bundle, resulting in a IllegalArgumentException while validating data under {@link |
| * Bundle#containsKey(String)}. |
| */ |
| try { |
| callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS); |
| return false; |
| } catch (IllegalArgumentException e) { |
| LogUtil.e( |
| "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e); |
| return true; |
| } |
| } |
| |
| protected void updateFromCallExtras(Bundle callExtras) { |
| if (callExtras == null || areCallExtrasCorrupted(callExtras)) { |
| /** |
| * If the bundle is corrupted, abandon information update as a work around. These are not |
| * critical for the dialer to function. |
| */ |
| return; |
| } |
| // Check for a change in the child address and notify any listeners. |
| if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) { |
| String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS); |
| if (!Objects.equals(childNumber, mChildNumber)) { |
| mChildNumber = childNumber; |
| for (DialerCallListener listener : mListeners) { |
| listener.onDialerCallChildNumberChange(); |
| } |
| } |
| } |
| |
| // Last forwarded number comes in as an array of strings. We want to choose the |
| // last item in the array. The forwarding numbers arrive independently of when the |
| // call is originally set up, so we need to notify the the UI of the change. |
| if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) { |
| ArrayList<String> lastForwardedNumbers = |
| callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER); |
| |
| if (lastForwardedNumbers != null) { |
| String lastForwardedNumber = null; |
| if (!lastForwardedNumbers.isEmpty()) { |
| lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1); |
| } |
| |
| if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) { |
| mLastForwardedNumber = lastForwardedNumber; |
| for (DialerCallListener listener : mListeners) { |
| listener.onDialerCallLastForwardedNumberChange(); |
| } |
| } |
| } |
| } |
| |
| // DialerCall subject is present in the extras at the start of call, so we do not need to |
| // notify any other listeners of this. |
| if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) { |
| String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT); |
| if (!Objects.equals(mCallSubject, callSubject)) { |
| mCallSubject = callSubject; |
| } |
| } |
| } |
| |
| /** |
| * Determines if a received upgrade to video request should be cancelled. This can happen if |
| * another InCall UI responds to the upgrade to video request. |
| * |
| * @param newVideoState The new video state. |
| */ |
| private void maybeCancelVideoUpgrade(int newVideoState) { |
| boolean isVideoStateChanged = mVideoState != newVideoState; |
| |
| if (mSessionModificationState |
| == DialerCall.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST |
| && isVideoStateChanged) { |
| |
| LogUtil.i("DialerCall.maybeCancelVideoUpgrade", "cancelling upgrade notification"); |
| setSessionModificationState(DialerCall.SESSION_MODIFICATION_STATE_NO_REQUEST); |
| } |
| mVideoState = newVideoState; |
| } |
| |
| public String getId() { |
| return mId; |
| } |
| |
| public boolean hasShownWiFiToLteHandoverToast() { |
| return hasShownWiFiToLteHandoverToast; |
| } |
| |
| public void setHasShownWiFiToLteHandoverToast() { |
| hasShownWiFiToLteHandoverToast = true; |
| } |
| |
| public boolean showWifiHandoverAlertAsToast() { |
| return doNotShowDialogForHandoffToWifiFailure; |
| } |
| |
| public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) { |
| doNotShowDialogForHandoffToWifiFailure = bool; |
| } |
| |
| public long getTimeAddedMs() { |
| return mTimeAddedMs; |
| } |
| |
| @Nullable |
| public String getNumber() { |
| return TelecomCallUtil.getNumber(mTelecomCall); |
| } |
| |
| public void blockCall() { |
| mTelecomCall.reject(false, null); |
| setState(State.BLOCKED); |
| } |
| |
| @Nullable |
| public Uri getHandle() { |
| return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle(); |
| } |
| |
| public boolean isEmergencyCall() { |
| return mIsEmergencyCall; |
| } |
| |
| public boolean isPotentialEmergencyCallback() { |
| // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system |
| // is actually in emergency callback mode (ie data is disabled). |
| if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) { |
| return true; |
| } |
| // We want to treat any incoming call that arrives a short time after an outgoing emergency call |
| // as a potential emergency callback. |
| if (getExtras() != null |
| && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0) |
| > 0) { |
| long lastEmergencyCallMillis = |
| getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0); |
| if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| boolean isInEmergencyCallbackWindow(long timestampMillis) { |
| long emergencyCallbackWindowMillis = |
| ConfigProviderBindings.get(mContext) |
| .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5)); |
| return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis; |
| } |
| |
| public int getState() { |
| if (mTelecomCall != null && mTelecomCall.getParent() != null) { |
| return State.CONFERENCED; |
| } else { |
| return mState; |
| } |
| } |
| |
| public void setState(int state) { |
| mState = state; |
| if (mState == State.INCOMING) { |
| mLogState.isIncoming = true; |
| } else if (mState == State.DISCONNECTED) { |
| mLogState.duration = |
| getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis(); |
| } |
| } |
| |
| public int getNumberPresentation() { |
| return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation(); |
| } |
| |
| public int getCnapNamePresentation() { |
| return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation(); |
| } |
| |
| @Nullable |
| public String getCnapName() { |
| return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName(); |
| } |
| |
| public Bundle getIntentExtras() { |
| return mTelecomCall.getDetails().getIntentExtras(); |
| } |
| |
| @Nullable |
| public Bundle getExtras() { |
| return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras(); |
| } |
| |
| /** @return The child number for the call, or {@code null} if none specified. */ |
| public String getChildNumber() { |
| return mChildNumber; |
| } |
| |
| /** @return The last forwarded number for the call, or {@code null} if none specified. */ |
| public String getLastForwardedNumber() { |
| return mLastForwardedNumber; |
| } |
| |
| /** @return The call subject, or {@code null} if none specified. */ |
| public String getCallSubject() { |
| return mCallSubject; |
| } |
| |
| /** |
| * @return {@code true} if the call's phone account supports call subjects, {@code false} |
| * otherwise. |
| */ |
| public boolean isCallSubjectSupported() { |
| return mIsCallSubjectSupported; |
| } |
| |
| /** Returns call disconnect cause, defined by {@link DisconnectCause}. */ |
| public DisconnectCause getDisconnectCause() { |
| if (mState == State.DISCONNECTED || mState == State.IDLE) { |
| return mDisconnectCause; |
| } |
| |
| return new DisconnectCause(DisconnectCause.UNKNOWN); |
| } |
| |
| public void setDisconnectCause(DisconnectCause disconnectCause) { |
| mDisconnectCause = disconnectCause; |
| mLogState.disconnectCause = mDisconnectCause; |
| } |
| |
| /** Returns the possible text message responses. */ |
| public List<String> getCannedSmsResponses() { |
| return mTelecomCall.getCannedTextResponses(); |
| } |
| |
| /** Checks if the call supports the given set of capabilities supplied as a bit mask. */ |
| public boolean can(int capabilities) { |
| int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities(); |
| |
| if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) { |
| // We allow you to merge if the capabilities allow it or if it is a call with |
| // conferenceable calls. |
| if (mTelecomCall.getConferenceableCalls().isEmpty() |
| && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) { |
| // Cannot merge calls if there are no calls to merge with. |
| return false; |
| } |
| capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE; |
| } |
| return (capabilities == (capabilities & supportedCapabilities)); |
| } |
| |
| public boolean hasProperty(int property) { |
| return mTelecomCall.getDetails().hasProperty(property); |
| } |
| |
| public String getUniqueCallId() { |
| return uniqueCallId; |
| } |
| |
| /** Gets the time when the call first became active. */ |
| public long getConnectTimeMillis() { |
| return mTelecomCall.getDetails().getConnectTimeMillis(); |
| } |
| |
| public boolean isConferenceCall() { |
| return hasProperty(Call.Details.PROPERTY_CONFERENCE); |
| } |
| |
| @Nullable |
| public GatewayInfo getGatewayInfo() { |
| return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo(); |
| } |
| |
| @Nullable |
| public PhoneAccountHandle getAccountHandle() { |
| return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle(); |
| } |
| |
| /** |
| * @return The {@link VideoCall} instance associated with the {@link Call}. Will return {@code |
| * null} until {@link #updateFromTelecomCall(boolean)} has registered a valid callback on the |
| * {@link VideoCall}. |
| */ |
| public VideoCall getVideoCall() { |
| return mTelecomCall == null || !mIsVideoCallCallbackRegistered |
| ? null |
| : mTelecomCall.getVideoCall(); |
| } |
| |
| public List<String> getChildCallIds() { |
| return mChildCallIds; |
| } |
| |
| public String getParentId() { |
| Call parentCall = mTelecomCall.getParent(); |
| if (parentCall != null) { |
| return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId(); |
| } |
| return null; |
| } |
| |
| public int getVideoState() { |
| return mTelecomCall.getDetails().getVideoState(); |
| } |
| |
| public boolean isVideoCall() { |
| return CallUtil.isVideoEnabled(mContext) && VideoUtils.isVideoCall(getVideoState()); |
| } |
| |
| /** |
| * Determines if the call handle is an emergency number or not and caches the result to avoid |
| * repeated calls to isEmergencyNumber. |
| */ |
| private void updateEmergencyCallState() { |
| mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall); |
| } |
| |
| /** |
| * Gets the video state which was requested via a session modification request. |
| * |
| * @return The video state. |
| */ |
| public int getRequestedVideoState() { |
| return mRequestedVideoState; |
| } |
| |
| /** |
| * Handles incoming session modification requests. Stores the pending video request and sets the |
| * session modification state to {@link |
| * DialerCall#SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST} so that we can keep |
| * track of the fact the request was received. Only upgrade requests require user confirmation and |
| * will be handled by this method. The remote user can turn off their own camera without |
| * confirmation. |
| * |
| * @param videoState The requested video state. |
| */ |
| public void setRequestedVideoState(int videoState) { |
| LogUtil.v("DialerCall.setRequestedVideoState", "videoState: " + videoState); |
| if (videoState == getVideoState()) { |
| LogUtil.e("DialerCall.setRequestedVideoState", "clearing session modification state"); |
| setSessionModificationState(DialerCall.SESSION_MODIFICATION_STATE_NO_REQUEST); |
| return; |
| } |
| |
| mRequestedVideoState = videoState; |
| setSessionModificationState( |
| DialerCall.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST); |
| for (DialerCallListener listener : mListeners) { |
| listener.onDialerCallUpgradeToVideo(); |
| } |
| |
| LogUtil.i( |
| "DialerCall.setRequestedVideoState", |
| "mSessionModificationState: %d, videoState: %d", |
| mSessionModificationState, |
| videoState); |
| update(); |
| } |
| |
| /** |
| * Gets the current video session modification state. |
| * |
| * @return The session modification state. |
| */ |
| @SessionModificationState |
| public int getSessionModificationState() { |
| return mSessionModificationState; |
| } |
| |
| /** |
| * Set the session modification state. Used to keep track of pending video session modification |
| * operations and to inform listeners of these changes. |
| * |
| * @param state the new session modification state. |
| */ |
| public void setSessionModificationState(@SessionModificationState int state) { |
| boolean hasChanged = mSessionModificationState != state; |
| if (hasChanged) { |
| LogUtil.i( |
| "DialerCall.setSessionModificationState", "%d -> %d", mSessionModificationState, state); |
| mSessionModificationState = state; |
| for (DialerCallListener listener : mListeners) { |
| listener.onDialerCallSessionModificationStateChange(state); |
| } |
| } |
| } |
| |
| public LogState getLogState() { |
| return mLogState; |
| } |
| |
| /** |
| * Determines if the call is an external call. |
| * |
| * <p>An external call is one which does not exist locally for the {@link |
| * android.telecom.ConnectionService} it is associated with. |
| * |
| * <p>External calls are only supported in N and higher. |
| * |
| * @return {@code true} if the call is an external call, {@code false} otherwise. |
| */ |
| public boolean isExternalCall() { |
| return VERSION.SDK_INT >= VERSION_CODES.N |
| && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL); |
| } |
| |
| /** |
| * Determines if the external call is pullable. |
| * |
| * <p>An external call is one which does not exist locally for the {@link |
| * android.telecom.ConnectionService} it is associated with. An external call may be "pullable", |
| * which means that the user can request it be transferred to the current device. |
| * |
| * <p>External calls are only supported in N and higher. |
| * |
| * @return {@code true} if the call is an external call, {@code false} otherwise. |
| */ |
| public boolean isPullableExternalCall() { |
| return VERSION.SDK_INT >= VERSION_CODES.N |
| && (mTelecomCall.getDetails().getCallCapabilities() |
| & CallCompat.Details.CAPABILITY_CAN_PULL_CALL) |
| == CallCompat.Details.CAPABILITY_CAN_PULL_CALL; |
| } |
| |
| /** |
| * Determines if answering this call will cause an ongoing video call to be dropped. |
| * |
| * @return {@code true} if answering this call will drop an ongoing video call, {@code false} |
| * otherwise. |
| */ |
| public boolean answeringDisconnectsForegroundVideoCall() { |
| Bundle extras = getExtras(); |
| if (extras == null |
| || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) { |
| return false; |
| } |
| return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL); |
| } |
| |
| private void parseCallSpecificAppData() { |
| if (isExternalCall()) { |
| return; |
| } |
| |
| mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras()); |
| if (mLogState.callSpecificAppData == null) { |
| mLogState.callSpecificAppData = new CallSpecificAppData(); |
| mLogState.callSpecificAppData.callInitiationType = |
| CallInitiationType.Type.EXTERNAL_INITIATION; |
| } |
| if (getState() == State.INCOMING) { |
| mLogState.callSpecificAppData.callInitiationType = |
| CallInitiationType.Type.INCOMING_INITIATION; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| if (mTelecomCall == null) { |
| // This should happen only in testing since otherwise we would never have a null |
| // Telecom call. |
| return String.valueOf(mId); |
| } |
| |
| return String.format( |
| Locale.US, |
| "[%s, %s, %s, %s, children:%s, parent:%s, " |
| + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, VideoSettings:%s]", |
| mId, |
| State.toString(getState()), |
| Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()), |
| Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()), |
| mChildCallIds, |
| getParentId(), |
| this.mTelecomCall.getConferenceableCalls(), |
| VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()), |
| mSessionModificationState, |
| getVideoSettings()); |
| } |
| |
| public String toSimpleString() { |
| return super.toString(); |
| } |
| |
| @CallHistoryStatus |
| public int getCallHistoryStatus() { |
| return mCallHistoryStatus; |
| } |
| |
| public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) { |
| mCallHistoryStatus = callHistoryStatus; |
| } |
| |
| public boolean didShowCameraPermission() { |
| return didShowCameraPermission; |
| } |
| |
| public void setDidShowCameraPermission(boolean didShow) { |
| didShowCameraPermission = didShow; |
| } |
| |
| public boolean isInGlobalSpamList() { |
| return isInGlobalSpamList; |
| } |
| |
| public void setIsInGlobalSpamList(boolean inSpamList) { |
| isInGlobalSpamList = inSpamList; |
| } |
| |
| public boolean isInUserSpamList() { |
| return isInUserSpamList; |
| } |
| |
| public void setIsInUserSpamList(boolean inSpamList) { |
| isInUserSpamList = inSpamList; |
| } |
| |
| public boolean isInUserWhiteList() { |
| return isInUserWhiteList; |
| } |
| |
| public void setIsInUserWhiteList(boolean inWhiteList) { |
| isInUserWhiteList = inWhiteList; |
| } |
| |
| public boolean isSpam() { |
| return mIsSpam; |
| } |
| |
| public void setSpam(boolean isSpam) { |
| mIsSpam = isSpam; |
| } |
| |
| public boolean isBlocked() { |
| return mIsBlocked; |
| } |
| |
| public void setBlockedStatus(boolean isBlocked) { |
| mIsBlocked = isBlocked; |
| } |
| |
| public boolean isRemotelyHeld() { |
| return isRemotelyHeld; |
| } |
| |
| public boolean isIncoming() { |
| return mLogState.isIncoming; |
| } |
| |
| public LatencyReport getLatencyReport() { |
| return mLatencyReport; |
| } |
| |
| public void unregisterCallback() { |
| mTelecomCall.unregisterCallback(mTelecomCallCallback); |
| } |
| |
| public void acceptUpgradeRequest(int videoState) { |
| LogUtil.i("DialerCall.acceptUpgradeRequest", "videoState: " + videoState); |
| VideoProfile videoProfile = new VideoProfile(videoState); |
| getVideoCall().sendSessionModifyResponse(videoProfile); |
| setSessionModificationState(DialerCall.SESSION_MODIFICATION_STATE_NO_REQUEST); |
| } |
| |
| public void declineUpgradeRequest() { |
| LogUtil.i("DialerCall.declineUpgradeRequest", ""); |
| VideoProfile videoProfile = new VideoProfile(getVideoState()); |
| getVideoCall().sendSessionModifyResponse(videoProfile); |
| setSessionModificationState(DialerCall.SESSION_MODIFICATION_STATE_NO_REQUEST); |
| } |
| |
| public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) { |
| LogUtil.i( |
| "DialerCall.phoneAccountSelected", |
| "accountHandle: %s, setDefault: %b", |
| accountHandle, |
| setDefault); |
| mTelecomCall.phoneAccountSelected(accountHandle, setDefault); |
| } |
| |
| public void disconnect() { |
| LogUtil.i("DialerCall.disconnect", ""); |
| setState(DialerCall.State.DISCONNECTING); |
| for (DialerCallListener listener : mListeners) { |
| listener.onDialerCallUpdate(); |
| } |
| mTelecomCall.disconnect(); |
| } |
| |
| public void hold() { |
| LogUtil.i("DialerCall.hold", ""); |
| mTelecomCall.hold(); |
| } |
| |
| public void unhold() { |
| LogUtil.i("DialerCall.unhold", ""); |
| mTelecomCall.unhold(); |
| } |
| |
| public void splitFromConference() { |
| LogUtil.i("DialerCall.splitFromConference", ""); |
| mTelecomCall.splitFromConference(); |
| } |
| |
| public void answer(int videoState) { |
| LogUtil.i("DialerCall.answer", "videoState: " + videoState); |
| mTelecomCall.answer(videoState); |
| } |
| |
| public void reject(boolean rejectWithMessage, String message) { |
| LogUtil.i("DialerCall.reject", ""); |
| mTelecomCall.reject(rejectWithMessage, message); |
| } |
| |
| /** Return the string label to represent the call provider */ |
| public String getCallProviderLabel() { |
| if (callProviderLabel == null) { |
| PhoneAccount account = getPhoneAccount(); |
| if (account != null && !TextUtils.isEmpty(account.getLabel())) { |
| List<PhoneAccountHandle> accounts = |
| mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts(); |
| if (accounts != null && accounts.size() > 1) { |
| callProviderLabel = account.getLabel().toString(); |
| } |
| } |
| if (callProviderLabel == null) { |
| callProviderLabel = ""; |
| } |
| } |
| return callProviderLabel; |
| } |
| |
| private PhoneAccount getPhoneAccount() { |
| PhoneAccountHandle accountHandle = getAccountHandle(); |
| if (accountHandle == null) { |
| return null; |
| } |
| return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle); |
| } |
| |
| public String getCallbackNumber() { |
| if (callbackNumber == null) { |
| // Show the emergency callback number if either: |
| // 1. This is an emergency call. |
| // 2. The phone is in Emergency Callback Mode, which means we should show the callback |
| // number. |
| boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE); |
| |
| if (isEmergencyCall() || showCallbackNumber) { |
| callbackNumber = getSubscriptionNumber(); |
| } else { |
| StatusHints statusHints = getTelecomCall().getDetails().getStatusHints(); |
| if (statusHints != null) { |
| Bundle extras = statusHints.getExtras(); |
| if (extras != null) { |
| callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER); |
| } |
| } |
| } |
| |
| String simNumber = |
| mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle()); |
| if (!showCallbackNumber && PhoneNumberUtils.compare(callbackNumber, simNumber)) { |
| LogUtil.v( |
| "DialerCall.getCallbackNumber", |
| "numbers are the same (and callback number is not being forced to show);" |
| + " not showing the callback number"); |
| callbackNumber = ""; |
| } |
| if (callbackNumber == null) { |
| callbackNumber = ""; |
| } |
| } |
| return callbackNumber; |
| } |
| |
| private String getSubscriptionNumber() { |
| // If it's an emergency call, and they're not populating the callback number, |
| // then try to fall back to the phone sub info (to hopefully get the SIM's |
| // number directly from the telephony layer). |
| PhoneAccountHandle accountHandle = getAccountHandle(); |
| if (accountHandle != null) { |
| PhoneAccount account = |
| mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle); |
| if (account != null) { |
| return getNumberFromHandle(account.getSubscriptionAddress()); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN} |
| * means there is no result. |
| */ |
| @IntDef({ |
| CALL_HISTORY_STATUS_UNKNOWN, |
| CALL_HISTORY_STATUS_PRESENT, |
| CALL_HISTORY_STATUS_NOT_PRESENT |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface CallHistoryStatus {} |
| |
| /* Defines different states of this call */ |
| public static class State { |
| |
| public static final int INVALID = 0; |
| public static final int NEW = 1; /* The call is new. */ |
| public static final int IDLE = 2; /* The call is idle. Nothing active */ |
| public static final int ACTIVE = 3; /* There is an active call */ |
| public static final int INCOMING = 4; /* A normal incoming phone call */ |
| public static final int CALL_WAITING = 5; /* Incoming call while another is active */ |
| public static final int DIALING = 6; /* An outgoing call during dial phase */ |
| public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */ |
| public static final int ONHOLD = 8; /* An active phone call placed on hold */ |
| public static final int DISCONNECTING = 9; /* A call is being ended. */ |
| public static final int DISCONNECTED = 10; /* State after a call disconnects */ |
| public static final int CONFERENCED = 11; /* DialerCall part of a conference call */ |
| public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */ |
| public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */ |
| public static final int BLOCKED = 14; /* The number was found on the block list */ |
| public static final int PULLING = 15; /* An external call being pulled to the device */ |
| |
| public static boolean isConnectingOrConnected(int state) { |
| switch (state) { |
| case ACTIVE: |
| case INCOMING: |
| case CALL_WAITING: |
| case CONNECTING: |
| case DIALING: |
| case PULLING: |
| case REDIALING: |
| case ONHOLD: |
| case CONFERENCED: |
| return true; |
| default: |
| } |
| return false; |
| } |
| |
| public static boolean isDialing(int state) { |
| return state == DIALING || state == PULLING || state == REDIALING; |
| } |
| |
| public static String toString(int state) { |
| switch (state) { |
| case INVALID: |
| return "INVALID"; |
| case NEW: |
| return "NEW"; |
| case IDLE: |
| return "IDLE"; |
| case ACTIVE: |
| return "ACTIVE"; |
| case INCOMING: |
| return "INCOMING"; |
| case CALL_WAITING: |
| return "CALL_WAITING"; |
| case DIALING: |
| return "DIALING"; |
| case PULLING: |
| return "PULLING"; |
| case REDIALING: |
| return "REDIALING"; |
| case ONHOLD: |
| return "ONHOLD"; |
| case DISCONNECTING: |
| return "DISCONNECTING"; |
| case DISCONNECTED: |
| return "DISCONNECTED"; |
| case CONFERENCED: |
| return "CONFERENCED"; |
| case SELECT_PHONE_ACCOUNT: |
| return "SELECT_PHONE_ACCOUNT"; |
| case CONNECTING: |
| return "CONNECTING"; |
| case BLOCKED: |
| return "BLOCKED"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| } |
| |
| /** |
| * Defines different states of session modify requests, which are used to upgrade to video, or |
| * downgrade to audio. |
| */ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| SESSION_MODIFICATION_STATE_NO_REQUEST, |
| SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE, |
| SESSION_MODIFICATION_STATE_REQUEST_FAILED, |
| SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST, |
| SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT, |
| SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_FAILED, |
| SESSION_MODIFICATION_STATE_REQUEST_REJECTED, |
| SESSION_MODIFICATION_STATE_WAITING_FOR_RESPONSE |
| }) |
| public @interface SessionModificationState {} |
| |
| public static final int SESSION_MODIFICATION_STATE_NO_REQUEST = 0; |
| public static final int SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE = 1; |
| public static final int SESSION_MODIFICATION_STATE_REQUEST_FAILED = 2; |
| public static final int SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3; |
| public static final int SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4; |
| public static final int SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_FAILED = 5; |
| public static final int SESSION_MODIFICATION_STATE_REQUEST_REJECTED = 6; |
| public static final int SESSION_MODIFICATION_STATE_WAITING_FOR_RESPONSE = 7; |
| |
| public static class VideoSettings { |
| |
| public static final int CAMERA_DIRECTION_UNKNOWN = -1; |
| public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT; |
| public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK; |
| |
| private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN; |
| |
| /** |
| * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, the video |
| * state of the call should be used to infer the camera direction. |
| * |
| * @see {@link CameraCharacteristics#LENS_FACING_FRONT} |
| * @see {@link CameraCharacteristics#LENS_FACING_BACK} |
| */ |
| public int getCameraDir() { |
| return mCameraDirection; |
| } |
| |
| /** |
| * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, the video |
| * state of the call should be used to infer the camera direction. |
| * |
| * @see {@link CameraCharacteristics#LENS_FACING_FRONT} |
| * @see {@link CameraCharacteristics#LENS_FACING_BACK} |
| */ |
| public void setCameraDir(int cameraDirection) { |
| if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING |
| || cameraDirection == CAMERA_DIRECTION_BACK_FACING) { |
| mCameraDirection = cameraDirection; |
| } else { |
| mCameraDirection = CAMERA_DIRECTION_UNKNOWN; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "(CameraDir:" + getCameraDir() + ")"; |
| } |
| } |
| |
| /** |
| * Tracks any state variables that is useful for logging. There is some amount of overlap with |
| * existing call member variables, but this duplication helps to ensure that none of these logging |
| * variables will interface with/and affect call logic. |
| */ |
| public static class LogState { |
| |
| public DisconnectCause disconnectCause; |
| public boolean isIncoming = false; |
| public int contactLookupResult = ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE; |
| public CallSpecificAppData callSpecificAppData; |
| // If this was a conference call, the total number of calls involved in the conference. |
| public int conferencedCalls = 0; |
| public long duration = 0; |
| public boolean isLogged = false; |
| |
| private static String lookupToString(int lookupType) { |
| switch (lookupType) { |
| case ContactLookupResult.Type.LOCAL_CONTACT: |
| return "Local"; |
| case ContactLookupResult.Type.LOCAL_CACHE: |
| return "Cache"; |
| case ContactLookupResult.Type.REMOTE: |
| return "Remote"; |
| case ContactLookupResult.Type.EMERGENCY: |
| return "Emergency"; |
| case ContactLookupResult.Type.VOICEMAIL: |
| return "Voicemail"; |
| default: |
| return "Not found"; |
| } |
| } |
| |
| private static String initiationToString(CallSpecificAppData callSpecificAppData) { |
| if (callSpecificAppData == null) { |
| return "null"; |
| } |
| switch (callSpecificAppData.callInitiationType) { |
| case CallInitiationType.Type.INCOMING_INITIATION: |
| return "Incoming"; |
| case CallInitiationType.Type.DIALPAD: |
| return "Dialpad"; |
| case CallInitiationType.Type.SPEED_DIAL: |
| return "Speed Dial"; |
| case CallInitiationType.Type.REMOTE_DIRECTORY: |
| return "Remote Directory"; |
| case CallInitiationType.Type.SMART_DIAL: |
| return "Smart Dial"; |
| case CallInitiationType.Type.REGULAR_SEARCH: |
| return "Regular Search"; |
| case CallInitiationType.Type.CALL_LOG: |
| return "DialerCall Log"; |
| case CallInitiationType.Type.CALL_LOG_FILTER: |
| return "DialerCall Log Filter"; |
| case CallInitiationType.Type.VOICEMAIL_LOG: |
| return "Voicemail Log"; |
| case CallInitiationType.Type.CALL_DETAILS: |
| return "DialerCall Details"; |
| case CallInitiationType.Type.QUICK_CONTACTS: |
| return "Quick Contacts"; |
| case CallInitiationType.Type.EXTERNAL_INITIATION: |
| return "External"; |
| case CallInitiationType.Type.LAUNCHER_SHORTCUT: |
| return "Launcher Shortcut"; |
| default: |
| return "Unknown: " + callSpecificAppData.callInitiationType; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return String.format( |
| Locale.US, |
| "[" |
| + "%s, " // DisconnectCause toString already describes the object type |
| + "isIncoming: %s, " |
| + "contactLookup: %s, " |
| + "callInitiation: %s, " |
| + "duration: %s" |
| + "]", |
| disconnectCause, |
| isIncoming, |
| lookupToString(contactLookupResult), |
| initiationToString(callSpecificAppData), |
| duration); |
| } |
| } |
| |
| /** Called when canned text responses have been loaded. */ |
| public interface CannedTextResponsesLoadedListener { |
| void onCannedTextResponsesLoaded(DialerCall call); |
| } |
| } |