diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 50c3c78ce9d44..28f1bcf3dae7c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -13785,6 +13785,7 @@ package android.telephony { field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; // 0x1c field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_LEGACY_CALL_STATE_CHANGED = 36; // 0x24 field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_LINK_CAPACITY_ESTIMATE_CHANGED = 37; // 0x25 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_MEDIA_QUALITY_STATUS_CHANGED = 39; // 0x27 field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; // 0x3 field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_OEM_HOOK_RAW = 15; // 0xf field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; // 0x1d @@ -13820,6 +13821,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onLinkCapacityEstimateChanged(@NonNull java.util.List); } + public static interface TelephonyCallback.MediaQualityStatusChangedListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onMediaQualityStatusChanged(@NonNull android.telephony.ims.MediaQualityStatus); + } + public static interface TelephonyCallback.OutgoingEmergencyCallListener { method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int); } @@ -15362,6 +15367,37 @@ package android.telephony.ims { method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile); } + public final class MediaQualityStatus implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getCallSessionId(); + method public int getMediaSessionType(); + method public long getRtpInactivityMillis(); + method public int getRtpJitterMillis(); + method @IntRange(from=0, to=100) public int getRtpPacketLossRate(); + method public int getTransportType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int MEDIA_SESSION_TYPE_AUDIO = 1; // 0x1 + field public static final int MEDIA_SESSION_TYPE_VIDEO = 2; // 0x2 + } + + public static final class MediaQualityStatus.Builder { + ctor public MediaQualityStatus.Builder(@NonNull String, int, int); + method @NonNull public android.telephony.ims.MediaQualityStatus build(); + method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpInactivityMillis(long); + method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpJitterMillis(int); + method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpPacketLossRate(@IntRange(from=0, to=100) int); + } + + public final class MediaThreshold implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public long[] getThresholdsRtpInactivityTimeMillis(); + method @NonNull public int[] getThresholdsRtpJitterMillis(); + method @NonNull public int[] getThresholdsRtpPacketLossRate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public class ProvisioningManager { method @NonNull public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int); @@ -15813,6 +15849,7 @@ package android.telephony.ims.feature { ctor public MmTelFeature(@NonNull java.util.concurrent.Executor); method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); method public void changeOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set); + method public void clearMediaThreshold(int); method @Nullable public android.telephony.ims.ImsCallProfile createCallProfile(int, int); method @Nullable public android.telephony.ims.stub.ImsCallSessionImplBase createCallSession(@NonNull android.telephony.ims.ImsCallProfile); method @NonNull public android.telephony.ims.stub.ImsEcbmImplBase getEcbm(); @@ -15822,6 +15859,7 @@ package android.telephony.ims.feature { method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities); method @Deprecated public final void notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull android.os.Bundle); method @Nullable public final android.telephony.ims.ImsCallSessionListener notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull String, @NonNull android.os.Bundle); + method public final void notifyMediaQualityStatusChanged(@NonNull android.telephony.ims.MediaQualityStatus); method public final void notifyRejectedCall(@NonNull android.telephony.ims.ImsCallProfile, @NonNull android.telephony.ims.ImsReasonInfo); method public void notifySrvccCanceled(); method public void notifySrvccCompleted(); @@ -15832,7 +15870,9 @@ package android.telephony.ims.feature { method public void onFeatureRemoved(); method public boolean queryCapabilityConfiguration(int, int); method @NonNull public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus(); + method @Nullable public android.telephony.ims.MediaQualityStatus queryMediaQualityStatus(int); method public final void setCallAudioHandler(int); + method public void setMediaThreshold(int, @NonNull android.telephony.ims.MediaThreshold); method public void setTerminalBasedCallWaitingStatus(boolean); method public void setUiTtyMode(int, @Nullable android.os.Message); method public int shouldProcessCall(@NonNull String[]); diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index dded76c9a97ab..18fdb838bfab5 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -34,6 +34,7 @@ import android.telephony.Annotation.SrvccState; import android.telephony.TelephonyManager.DataEnabledReason; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; @@ -1664,6 +1665,10 @@ public class PhoneStateListener { List linkCapacityEstimateList) { // default implementation empty } + + public final void onMediaQualityStatusChanged(MediaQualityStatus mediaQualityStatus) { + // not support. Can't override. Use TelephonyCallback. + } } private void log(String s) { diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index e90ae757abd23..a50e6db5d3d9e 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -27,6 +27,8 @@ import android.os.Binder; import android.os.Build; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; +import android.telephony.ims.MediaThreshold; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -591,6 +593,19 @@ public class TelephonyCallback { */ public static final int EVENT_TRIGGER_NOTIFY_ANBR = 38; + /** + * Event for changes to the media quality status + * + *

Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} + * + * @see MediaQualityStatusChangedListener#onMediaQualityStatusChanged + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public static final int EVENT_MEDIA_QUALITY_STATUS_CHANGED = 39; + /** * @hide */ @@ -632,7 +647,8 @@ public class TelephonyCallback { EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED, EVENT_LEGACY_CALL_STATE_CHANGED, EVENT_LINK_CAPACITY_ESTIMATE_CHANGED, - EVENT_TRIGGER_NOTIFY_ANBR + EVENT_TRIGGER_NOTIFY_ANBR, + EVENT_MEDIA_QUALITY_STATUS_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface TelephonyEvent { @@ -1516,6 +1532,30 @@ public class TelephonyCallback { @NonNull List linkCapacityEstimateList); } + /** + * Interface for media quality status changed listener. + * + * @hide + */ + @SystemApi + public interface MediaQualityStatusChangedListener { + /** + * Callback invoked when the media quality status of IMS call changes. This call back + * means current media quality status crosses at least one of threshold values in {@link + * MediaThreshold}. Listener needs to get quality information & check whether it crossed + * listener's threshold. + * + *

Currently thresholds for this indication can be configurable by CARRIER_CONFIG + * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_PACKET_LOSS_RATE_INT} + * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_INACTIVITY_TIME_IN_MILLIS_INT} + * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT} + * + * @param mediaQualityStatus The media quality status currently measured. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + void onMediaQualityStatusChanged(@NonNull MediaQualityStatus mediaQualityStatus); + } + /** * The callback methods need to be called on the handler thread where * this object was created. If the binder did that for us it'd be nice. @@ -1873,5 +1913,16 @@ public class TelephonyCallback { () -> mExecutor.execute(() -> listener.onLinkCapacityEstimateChanged( linkCapacityEstimateList))); } + + public void onMediaQualityStatusChanged( + MediaQualityStatus mediaQualityStatus) { + MediaQualityStatusChangedListener listener = + (MediaQualityStatusChangedListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> listener.onMediaQualityStatusChanged( + mediaQualityStatus))); + } } } diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 519647d805fda..8b24e07e9117f 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -40,6 +40,7 @@ import android.telephony.TelephonyManager.CarrierPrivilegesCallback; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; import android.util.ArraySet; import android.util.Log; @@ -544,6 +545,27 @@ public class TelephonyRegistryManager { } } + /** + * Notify change of media quality status {@link MediaQualityStatus} crosses media quality + * threshold + *

+ * Currently thresholds for this indication can be configurable by CARRIER_CONFIG + * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_PACKET_LOSS_RATE_INT} + * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_INACTIVITY_TIME_IN_MILLIS_INT} + * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT} + * + * @param status media quality status + */ + public void notifyMediaQualityStatusChanged( + int slotIndex, int subId, @NonNull MediaQualityStatus status) { + try { + sRegistry.notifyMediaQualityStatusChanged(slotIndex, subId, status); + } catch (RemoteException ex) { + // system server crash + throw ex.rethrowFromSystemServer(); + } + } + /** * Notify emergency number list changed on certain subscription. * @@ -1087,6 +1109,10 @@ public class TelephonyRegistryManager { eventList.add(TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED); } + if (telephonyCallback instanceof TelephonyCallback.MediaQualityStatusChangedListener) { + eventList.add(TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED); + } + return eventList; } diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 9cb2e68229f05..3e9f1cb3c1e50 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -31,6 +31,7 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; /** * {@hide} @@ -76,4 +77,5 @@ oneway interface IPhoneStateListener { void onDataEnabledChanged(boolean enabled, int reason); void onAllowedNetworkTypesChanged(in int reason, in long allowedNetworkType); void onLinkCapacityEstimateChanged(in List linkCapacityEstimateList); + void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 54936c67e4482..fd9239da98336 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -32,6 +32,8 @@ import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.MediaQualityStatus; + import com.android.internal.telephony.ICarrierConfigChangeListener; import com.android.internal.telephony.ICarrierPrivilegesCallback; import com.android.internal.telephony.IPhoneStateListener; @@ -92,6 +94,7 @@ interface ITelephonyRegistry { in EmergencyNumber emergencyNumber); void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId, int callNetworkType); + void notifyMediaQualityStatusChanged(int phoneId, int subId, in MediaQualityStatus status); void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo); void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity, String chosenPlmn, int domain, int causeCode, int additionalCauseCode); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index ee922f9b98dfb..e234c47e700ae 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -84,11 +84,13 @@ import android.telephony.data.ApnSetting; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LocalLog; import android.util.Pair; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; @@ -357,6 +359,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private CallQuality[] mCallQuality; + private List> mMediaQualityStatus; + private ArrayList> mCallStateLists; // network type of the call associated with the mCallStateLists and mCallQuality @@ -483,6 +487,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_DATA_ENABLED_CHANGED); REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED); + REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add( + TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED); } private boolean isLocationPermissionRequired(Set events) { @@ -715,6 +721,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { cutListToSize(mCarrierPrivilegeStates, mNumPhones); cutListToSize(mCarrierServiceStates, mNumPhones); cutListToSize(mCallStateLists, mNumPhones); + cutListToSize(mMediaQualityStatus, mNumPhones); return; } @@ -738,6 +745,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallDisconnectCause[i] = DisconnectCause.NOT_VALID; mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID; mCallQuality[i] = createCallQuality(); + mMediaQualityStatus.add(i, new SparseArray<>()); mCallStateLists.add(i, new ArrayList<>()); mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN; mPreciseCallState[i] = createPreciseCallState(); @@ -805,6 +813,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallDisconnectCause = new int[numPhones]; mCallPreciseDisconnectCause = new int[numPhones]; mCallQuality = new CallQuality[numPhones]; + mMediaQualityStatus = new ArrayList<>(); mCallNetworkType = new int[numPhones]; mCallStateLists = new ArrayList<>(); mPreciseDataConnectionStates = new ArrayList<>(); @@ -844,6 +853,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallDisconnectCause[i] = DisconnectCause.NOT_VALID; mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID; mCallQuality[i] = createCallQuality(); + mMediaQualityStatus.add(i, new SparseArray<>()); mCallStateLists.add(i, new ArrayList<>()); mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN; mPreciseCallState[i] = createPreciseCallState(); @@ -1392,6 +1402,32 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if (events.contains(TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED)) { + CallState callState = null; + for (CallState cs : mCallStateLists.get(r.phoneId)) { + if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) { + callState = cs; + break; + } + } + if (callState != null) { + String callId = callState.getImsCallSessionId(); + try { + MediaQualityStatus status = mMediaQualityStatus.get(r.phoneId).get( + MediaQualityStatus.MEDIA_SESSION_TYPE_AUDIO); + if (status != null && status.getCallSessionId().equals(callId)) { + r.callback.onMediaQualityStatusChanged(status); + } + status = mMediaQualityStatus.get(r.phoneId).get( + MediaQualityStatus.MEDIA_SESSION_TYPE_VIDEO); + if (status != null && status.getCallSessionId().equals(callId)) { + r.callback.onMediaQualityStatusChanged(status); + } + } catch (RemoteException ex) { + remove(r.binder); + } + } + } } } } @@ -2283,6 +2319,17 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } mCallStateLists.get(phoneId).add(builder.build()); } + boolean hasOngoingCall = false; + for (CallState cs : mCallStateLists.get(phoneId)) { + if (cs.getCallState() != PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED) { + hasOngoingCall = true; + break; + } + } + if (!hasOngoingCall) { + //no ongoing call. clear media quality status cached. + mMediaQualityStatus.get(phoneId).clear(); + } } for (Record r : mRecords) { @@ -2626,7 +2673,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } } - handleRemoveListLocked(); } } @@ -3127,6 +3173,55 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + @Override + public void notifyMediaQualityStatusChanged(int phoneId, int subId, MediaQualityStatus status) { + if (!checkNotifyPermission("notifyMediaQualityStatusChanged()")) { + return; + } + + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + if (mCallStateLists.get(phoneId).size() > 0) { + CallState callState = null; + for (CallState cs : mCallStateLists.get(phoneId)) { + if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) { + callState = cs; + break; + } + } + if (callState != null) { + String callSessionId = callState.getImsCallSessionId(); + if (callSessionId != null + && callSessionId.equals(status.getCallSessionId())) { + mMediaQualityStatus.get(phoneId) + .put(status.getMediaSessionType(), status); + } else { + log("SessionId mismatch active call:" + callSessionId + + " media quality:" + status.getCallSessionId()); + return; + } + } else { + log("There is no active call to report CallQaulity"); + return; + } + } + + for (Record r : mRecords) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED) + && idMatch(r, subId, phoneId)) { + try { + r.callback.onMediaQualityStatusChanged(status); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + @NeverCompile // Avoid size overhead of debugging code. @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index c4744ef21ead4..5d61dda19df26 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -45,6 +45,7 @@ import android.telephony.gba.UaSecurityProtocolIdentifier; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.ImsSsData; +import android.telephony.ims.MediaQualityStatus; import android.telephony.ims.RcsUceAdapter; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.feature.RcsFeature; @@ -6262,6 +6263,50 @@ public class CarrierConfigManager { public static final String KEY_DTMFNB_PAYLOAD_TYPE_INT_ARRAY = KEY_PREFIX + "dtmfnb_payload_type_int_array"; + /** + * This indicates the threshold for RTP packet loss rate in percentage. If measured packet + * loss rate crosses this, a callback with {@link MediaQualityStatus} will be invoked to + * listeners. + * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener} + * + *

+ * Valid threshold range : 0 ~ 100 + * + * @hide + */ + public static final String KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT = + KEY_PREFIX + "rtp_packet_loss_rate_threshold_int"; + + + /** + * This indicates the threshold for RTP jitter value in milliseconds (RFC3550). If measured + * jitter value crosses this, a callback with {@link MediaQualityStatus} will be invoked + * to listeners. + * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener} + * + *

+ * Valid threshold range : 0 ~ 10000 + * + * @hide + */ + public static final String KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT = + KEY_PREFIX + "rtp_jitter_threshold_millis_int"; + + /** + * This indicates the threshold for RTP inactivity time in milliseconds. If measured + * inactivity timer crosses this, a callback with {@link MediaQualityStatus} will be invoked + * to listeners. + * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener} + * + *

+ * Valid threshold range : 0 ~ 60000 + * + * @hide + */ + public static final String KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG = + KEY_PREFIX + "rtp_inactivity_time_threshold_millis_long"; + + /** @hide */ @IntDef({ BANDWIDTH_EFFICIENT, @@ -6748,6 +6793,9 @@ public class CarrierConfigManager { defaults.putInt(KEY_AUDIO_AS_BANDWIDTH_KBPS_INT, 41); defaults.putInt(KEY_AUDIO_RS_BANDWIDTH_BPS_INT, 600); defaults.putInt(KEY_AUDIO_RR_BANDWIDTH_BPS_INT, 2000); + defaults.putInt(KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT, 40); + defaults.putInt(KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT, 120); + defaults.putLong(KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG, 5000); defaults.putIntArray( KEY_AUDIO_INACTIVITY_CALL_END_REASONS_INT_ARRAY, diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.aidl b/telephony/java/android/telephony/ims/MediaQualityStatus.aidl new file mode 100644 index 0000000000000..e570f6c906c3a --- /dev/null +++ b/telephony/java/android/telephony/ims/MediaQualityStatus.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022 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 android.telephony.ims; + +parcelable MediaQualityStatus; \ No newline at end of file diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java new file mode 100644 index 0000000000000..62c289a2487d0 --- /dev/null +++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2022 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 android.telephony.ims; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.AccessNetworkConstants.TransportType; + +import java.util.Objects; + +/** + * A representation of Media quality status. + * + * @hide + */ +@SystemApi +public final class MediaQualityStatus implements Parcelable { + public static final int MEDIA_SESSION_TYPE_AUDIO = 1; + public static final int MEDIA_SESSION_TYPE_VIDEO = 2; + + private final String mImsCallSessionId; + private final int mMediaSessionType; + private final int mTransportType; + private final int mRtpPacketLossRate; + private final int mRtpJitter; + private final long mRtpInactivityTimeMillis; + + /** @hide */ + @IntDef( + value = { + MEDIA_SESSION_TYPE_AUDIO, + MEDIA_SESSION_TYPE_VIDEO, + }) + public @interface MediaSessionType {} + + /** + * Constructor for this + * + * @param imsCallSessionId IMS call session id of this quality status + * @param mediaSessionType media session type of this quality status + * @param transportType transport type of this quality status + * @param rtpPacketLossRate measured RTP packet loss rate + * @param rtpJitter measured RTP jitter value + * @param rptInactivityTimeMillis measured RTP inactivity time in milliseconds + */ + private MediaQualityStatus(@NonNull String imsCallSessionId, + @MediaSessionType int mediaSessionType, @TransportType int transportType, + int rtpPacketLossRate, int rtpJitter, long rptInactivityTimeMillis) { + mImsCallSessionId = imsCallSessionId; + mMediaSessionType = mediaSessionType; + mTransportType = transportType; + mRtpPacketLossRate = rtpPacketLossRate; + mRtpJitter = rtpJitter; + mRtpInactivityTimeMillis = rptInactivityTimeMillis; + } + + /** + * Retrieves call session ID for this quality status + */ + @NonNull + public String getCallSessionId() { + return mImsCallSessionId; + } + + /** + * Retrieves media session type of this quality status + */ + public @MediaSessionType int getMediaSessionType() { + return mMediaSessionType; + } + + + /** + * Retrieves Transport type for which this media quality was measured. + */ + public @TransportType int getTransportType() { + return mTransportType; + } + + /** + * Retrieves measured RTP packet loss rate in percentage. + */ + @IntRange(from = 0, to = 100) + public int getRtpPacketLossRate() { + return mRtpPacketLossRate; + } + + /** + * Retrieves measured RTP jitter(RFC3550) value in milliseconds + */ + public int getRtpJitterMillis() { + return mRtpJitter; + } + + /** + * Retrieves measured RTP inactivity time in milliseconds + */ + public long getRtpInactivityMillis() { + return mRtpInactivityTimeMillis; + } + + /** + * Creates a new instance of {@link MediaQualityStatus} from a parcel. + * @param in The parceled data to read. + */ + private MediaQualityStatus(@NonNull Parcel in) { + mImsCallSessionId = in.readString(); + mMediaSessionType = in.readInt(); + mTransportType = in.readInt(); + mRtpPacketLossRate = in.readInt(); + mRtpJitter = in.readInt(); + mRtpInactivityTimeMillis = in.readLong(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mImsCallSessionId); + dest.writeInt(mMediaSessionType); + dest.writeInt(mTransportType); + dest.writeInt(mRtpPacketLossRate); + dest.writeInt(mRtpJitter); + dest.writeLong(mRtpInactivityTimeMillis); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public MediaQualityStatus createFromParcel(@NonNull Parcel in) { + return new MediaQualityStatus(in); + } + + @Override + public MediaQualityStatus[] newArray(int size) { + return new MediaQualityStatus[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MediaQualityStatus that = (MediaQualityStatus) o; + return mImsCallSessionId != null && mImsCallSessionId.equals(that.mImsCallSessionId) + && mMediaSessionType == that.mMediaSessionType + && mTransportType == that.mTransportType + && mRtpPacketLossRate == that.mRtpPacketLossRate + && mRtpJitter == that.mRtpJitter + && mRtpInactivityTimeMillis == that.mRtpInactivityTimeMillis; + } + + @Override + public int hashCode() { + return Objects.hash(mImsCallSessionId, mMediaSessionType, mTransportType, + mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("MediaThreshold{mImsCallSessionId="); + sb.append(mImsCallSessionId); + sb.append(", mMediaSessionType="); + sb.append(mMediaSessionType); + sb.append(", mTransportType="); + sb.append(mTransportType); + sb.append(", mRtpPacketLossRate="); + sb.append(mRtpPacketLossRate); + sb.append(", mRtpJitter="); + sb.append(mRtpJitter); + sb.append(", mRtpInactivityTimeMillis="); + sb.append(mRtpInactivityTimeMillis); + sb.append("}"); + return sb.toString(); + } + + /** + * Provides a convenient way to set the fields of an {@link MediaQualityStatus} when creating a + * new instance. + * + *

The example below shows how you might create a new {@code RtpQualityStatus}: + * + *


+     *
+     * MediaQualityStatus = new MediaQualityStatus.Builder(
+     *                                          callSessionId, mediaSessionType, transportType)
+     *     .setRtpPacketLossRate(packetLossRate)
+     *     .setRtpJitter(jitter)
+     *     .setRtpInactivityMillis(inactivityTimeMillis)
+     *     .build();
+     * 
+ */ + public static final class Builder { + private final String mImsCallSessionId; + private final int mMediaSessionType; + private final int mTransportType; + private int mRtpPacketLossRate; + private int mRtpJitter; + private long mRtpInactivityTimeMillis; + + /** + * Default constructor for the Builder. + */ + public Builder( + @NonNull String imsCallSessionId, + @MediaSessionType int mediaSessionType, + int transportType) { + mImsCallSessionId = imsCallSessionId; + mMediaSessionType = mediaSessionType; + mTransportType = transportType; + } + + /** + * Set RTP packet loss info. + * + * @param packetLossRate RTP packet loss rate in percentage + * @return The same instance of the builder. + */ + @NonNull + public Builder setRtpPacketLossRate(@IntRange(from = 0, to = 100) int packetLossRate) { + this.mRtpPacketLossRate = packetLossRate; + return this; + } + + /** + * Set calculated RTP jitter(RFC3550) value in milliseconds. + * + * @param jitter calculated RTP jitter value. + * @return The same instance of the builder. + */ + @NonNull + public Builder setRtpJitterMillis(int jitter) { + this.mRtpJitter = jitter; + return this; + } + + /** + * Set measured RTP inactivity time. + * + * @param inactivityTimeMillis RTP inactivity time in Milliseconds. + * @return The same instance of the builder. + */ + @NonNull + public Builder setRtpInactivityMillis(long inactivityTimeMillis) { + this.mRtpInactivityTimeMillis = inactivityTimeMillis; + return this; + } + + /** + * Build the {@link MediaQualityStatus} + * + * @return the {@link MediaQualityStatus} object + */ + @NonNull + public MediaQualityStatus build() { + return new MediaQualityStatus( + mImsCallSessionId, + mMediaSessionType, + mTransportType, + mRtpPacketLossRate, + mRtpJitter, + mRtpInactivityTimeMillis); + } + } +} diff --git a/telephony/java/android/telephony/ims/MediaThreshold.aidl b/telephony/java/android/telephony/ims/MediaThreshold.aidl new file mode 100644 index 0000000000000..dede418fb0e23 --- /dev/null +++ b/telephony/java/android/telephony/ims/MediaThreshold.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022 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 android.telephony.ims; + +parcelable MediaThreshold; \ No newline at end of file diff --git a/telephony/java/android/telephony/ims/MediaThreshold.java b/telephony/java/android/telephony/ims/MediaThreshold.java new file mode 100644 index 0000000000000..343aa4aab023c --- /dev/null +++ b/telephony/java/android/telephony/ims/MediaThreshold.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2022 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 android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.ims.feature.MmTelFeature; + +import java.util.Arrays; +import java.util.Objects; +import java.util.TreeSet; + +/** + * A MediaThreshold represents a series of packet loss rate, jitter and rtp inactivity time + * thresholds which when crossed should result in a {@link MediaQualityStatus} report being + * generated by the {@link ImsService} via {@link MmTelFeature#notifyMediaQualityStatusChanged( + * MediaQualityStatus)} + * + *

+ * A {@link MediaQualityStatus} should be triggered when any of various + * attributes pass one of the thresholds defined here. + * + * @hide + */ +@SystemApi +public final class MediaThreshold implements Parcelable { + private final int[] mRtpPacketLossRate; + private final int[] mRtpJitter; + private final long[] mRtpInactivityTimeMillis; + + /** + * Retrieves threshold values for RTP packet loss rate in percentage. + * + * @return int array including threshold values for packet loss rate + * + * @hide + */ + @NonNull + @SystemApi + public int[] getThresholdsRtpPacketLossRate() { + return mRtpPacketLossRate; + } + + /** + * Retrieves threshold values for jitter(RFC3550) in milliseconds. + * + * @return int array including threshold values for RTP jitter. + */ + @NonNull + public int[] getThresholdsRtpJitterMillis() { + return mRtpJitter; + } + + /** + * Retrieves threshold values for RTP inactivity time in milliseconds. + * + * @return int array including threshold values for RTP inactivity time. + */ + @NonNull + public long[] getThresholdsRtpInactivityTimeMillis() { + return mRtpInactivityTimeMillis; + } + + private MediaThreshold( + int[] packetLossRateThresholds, + int[] jitterThresholds, + long[] inactivityTimeThresholds) { + mRtpPacketLossRate = packetLossRateThresholds; + mRtpJitter = jitterThresholds; + mRtpInactivityTimeMillis = inactivityTimeThresholds; + } + + /** + * Creates a new instance of {@link MediaThreshold} from a parcel. + * @param in The parceled data to read. + */ + private MediaThreshold(@NonNull Parcel in) { + mRtpPacketLossRate = in.createIntArray(); + mRtpJitter = in.createIntArray(); + mRtpInactivityTimeMillis = in.createLongArray(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeIntArray(mRtpPacketLossRate); + dest.writeIntArray(mRtpJitter); + dest.writeLongArray(mRtpInactivityTimeMillis); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public MediaThreshold createFromParcel(@NonNull Parcel in) { + return new MediaThreshold(in); + } + + @Override + public MediaThreshold[] newArray(int size) { + return new MediaThreshold[size]; + } + }; + + /** + * Returns whether the RTP packet loss rate threshold is valid or not. + * + * @param packetLossRate packet loss rate + * @return the threshold is valid or not. + * @hide + */ + public static boolean isValidRtpPacketLossRate(int packetLossRate) { + return (packetLossRate >= 0 && packetLossRate <= 100); + } + + /** + * Returns whether the RTP jitter threshold is valid or not. + * + * @param jitter jitter value in milliseconds + * @return the threshold is valid or not. + * @hide + */ + public static boolean isValidJitterMillis(int jitter) { + return (jitter >= 0 && jitter <= 10000); + } + + /** + * Returns whether the RTP packet loss rate threshold is valid or not. + * + * @param inactivityTime packet loss rate + * @return the threshold is valid or not. + * @hide + */ + public static boolean isValidRtpInactivityTimeMillis(long inactivityTime) { + return (inactivityTime >= 0 && inactivityTime <= 60000); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MediaThreshold that = (MediaThreshold) o; + return Arrays.equals(mRtpPacketLossRate, that.mRtpPacketLossRate) + && Arrays.equals(mRtpJitter, that.mRtpJitter) + && Arrays.equals(mRtpInactivityTimeMillis, that.mRtpInactivityTimeMillis); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(mRtpPacketLossRate), Arrays.hashCode(mRtpJitter), + Arrays.hashCode(mRtpInactivityTimeMillis)); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("MediaThreshold{mRtpPacketLossRate="); + for (int i : mRtpPacketLossRate) { + sb.append(" ").append(i); + } + sb.append(", mRtpJitter="); + for (int b : mRtpJitter) { + sb.append(" ").append(b); + } + sb.append(", mRtpInactivityTimeMillis="); + for (long i : mRtpInactivityTimeMillis) { + sb.append(" ").append(i); + } + sb.append("}"); + return sb.toString(); + } + + /** + * Provides a convenient way to set the fields of an {@link MediaThreshold} when creating a + * new instance. + * + *

The example below shows how you might create a new {@code RtpThreshold}: + * + *


+     *
+     * RtpThreshold = new RtpThreshold.Builder()
+     *     .setRtpSessionType({@link MediaQualityStatus#MEDIA_SESSION_TYPE_AUDIO} or
+     *                          {@link MediaQualityStatus#MEDIA_SESSION_TYPE_VIDEO})
+     *     .setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds)
+     *     .setThresholdsRtpJitterMillis(int[] jitterThresholds)
+     *     .setThresholdsRtpInactivityTimeMillis(int[] inactivityTimeThresholds)
+     *     .build();
+     * 
+ * + * @hide + */ + public static final class Builder { + private int[] mRtpPacketLossRate = null; + private int[] mRtpJitter = null; + private long[] mRtpInactivityTimeMillis = null; + + /** + * Default constructor for the Builder. + * + * @hide + */ + public Builder() { + } + + /** + * Set threshold values for RTP packet loss rate in percentage. + *

+ * The packet loss calculation should be done at least once per + * second. It should be calculated with at least the last 3 seconds + * of data. + * + * @param packetLossRateThresholds int array for threshold values. + * @return The same instance of the builder. + * + * @hide + */ + @NonNull + public Builder setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds) { + if (packetLossRateThresholds.length > 0) { + TreeSet thresholds = new TreeSet<>(); + for (Integer value : packetLossRateThresholds) { + if (isValidRtpPacketLossRate(value)) { + thresholds.add(value); + } + } + int[] targetArray = new int[thresholds.size()]; + int i = 0; + for (int element : thresholds) { + targetArray[i++] = element; + } + this.mRtpPacketLossRate = targetArray; + } else { + this.mRtpPacketLossRate = packetLossRateThresholds; + } + return this; + } + + + /** + * Set threshold values for RTP jitter in Milliseconds. + * + * @param jitterThresholds int array including threshold values for Jitter. + * @return The same instance of the builder. + * + * @hide + */ + @NonNull + public Builder setThresholdsRtpJitterMillis(int[] jitterThresholds) { + if (jitterThresholds.length > 0) { + TreeSet thresholds = new TreeSet<>(); + for (Integer value : jitterThresholds) { + if (isValidJitterMillis(value)) { + thresholds.add(value); + } + } + int[] targetArray = new int[thresholds.size()]; + int i = 0; + for (int element : thresholds) { + targetArray[i++] = element; + } + this.mRtpJitter = targetArray; + } else { + this.mRtpJitter = jitterThresholds; + } + return this; + } + + /** + * Set threshold values for RTP inactivity time. + * + * @param inactivityTimeThresholds int array including threshold + * values for RTP inactivity time. + * @return The same instance of the builder. + * + * @hide + */ + @NonNull + public Builder setThresholdsRtpInactivityTimeMillis(long[] inactivityTimeThresholds) { + if (inactivityTimeThresholds.length > 0) { + TreeSet thresholds = new TreeSet<>(); + for (Long value : inactivityTimeThresholds) { + if (isValidRtpInactivityTimeMillis(value)) { + thresholds.add(value); + } + } + long[] targetArray = new long[thresholds.size()]; + int i = 0; + for (long element : thresholds) { + targetArray[i++] = element; + } + this.mRtpInactivityTimeMillis = targetArray; + } else { + this.mRtpInactivityTimeMillis = inactivityTimeThresholds; + } + return this; + } + + /** + * Build the {@link MediaThreshold} + * + * @return the {@link MediaThreshold} object + * + * @hide + */ + @NonNull + public MediaThreshold build() { + mRtpPacketLossRate = mRtpPacketLossRate != null ? mRtpPacketLossRate : new int[0]; + mRtpJitter = mRtpJitter != null ? mRtpJitter : new int[0]; + mRtpInactivityTimeMillis = + mRtpInactivityTimeMillis != null ? mRtpInactivityTimeMillis : new long[0]; + return new MediaThreshold(mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis); + } + } +} diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl index cf7e9e168f67b..2f0eb6cb211bb 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl @@ -22,6 +22,8 @@ import android.telephony.ims.aidl.IImsSmsListener; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.ISrvccStartedCallback; import android.telephony.ims.feature.CapabilityChangeRequest; +import android.telephony.ims.MediaQualityStatus; +import android.telephony.ims.MediaThreshold; import android.telephony.ims.RtpHeaderExtensionType; import android.telephony.ims.ImsCallProfile; @@ -60,6 +62,9 @@ interface IImsMmTelFeature { oneway void notifySrvccCompleted(); oneway void notifySrvccFailed(); oneway void notifySrvccCanceled(); + oneway void setMediaQualityThreshold(int mediaSessionType, in MediaThreshold threshold); + MediaQualityStatus queryMediaQualityStatus(int mediaSessionType); + // SMS APIs void setSmsListener(IImsSmsListener l); oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl index b8701f15d0a80..e016c6153247d 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl @@ -20,6 +20,7 @@ import android.os.Bundle; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; import android.telephony.ims.aidl.IImsCallSessionListener; import android.telephony.ims.aidl.IImsTrafficSessionCallback; @@ -42,4 +43,5 @@ interface IImsMmTelListener { int trafficDirection, in IImsTrafficSessionCallback callback); oneway void onModifyImsTrafficSession(int token, int accessNetworkType); oneway void onStopImsTrafficSession(int token); + oneway void onMediaQualityStatusChanged(in MediaQualityStatus status); } diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index e21358832fbb3..bea409970ea18 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -34,6 +34,8 @@ import android.telephony.ims.ImsCallSessionListener; import android.telephony.ims.ImsException; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsService; +import android.telephony.ims.MediaQualityStatus; +import android.telephony.ims.MediaThreshold; import android.telephony.ims.RtpHeaderExtensionType; import android.telephony.ims.SrvccCall; import android.telephony.ims.aidl.IImsCallSessionListener; @@ -255,7 +257,7 @@ public class MmTelFeature extends ImsFeature { public void changeCapabilitiesConfiguration(CapabilityChangeRequest request, IImsCapabilityCallback c) { executeMethodAsyncNoException(() -> MmTelFeature.this - .requestChangeEnabledCapabilities(request, c), + .requestChangeEnabledCapabilities(request, c), "changeCapabilitiesConfiguration"); } @@ -266,6 +268,26 @@ public class MmTelFeature extends ImsFeature { capability, radioTech, c), "queryCapabilityConfiguration"); } + @Override + public void setMediaQualityThreshold(@MediaQualityStatus.MediaSessionType int sessionType, + MediaThreshold mediaThreshold) { + if (mediaThreshold != null) { + executeMethodAsyncNoException(() -> setMediaThreshold(sessionType, mediaThreshold), + "setMediaQualityThreshold"); + } else { + executeMethodAsyncNoException(() -> clearMediaThreshold(sessionType), + "clearMediaQualityThreshold"); + } + } + + @Override + public MediaQualityStatus queryMediaQualityStatus( + @MediaQualityStatus.MediaSessionType int sessionType) + throws RemoteException { + return executeMethodAsyncForResult(() -> MmTelFeature.this.queryMediaQualityStatus( + sessionType), "queryMediaQualityStatus"); + } + @Override public void setSmsListener(IImsSmsListener l) { executeMethodAsyncNoException(() -> MmTelFeature.this.setSmsListener(l), @@ -666,6 +688,17 @@ public class MmTelFeature extends ImsFeature { public void onStopImsTrafficSession(int token) { } + + /** + * Called when the IMS provider notifies {@link MediaQualityStatus}. + * + * @param status media quality status currently measured. + * @hide + */ + @Override + public void onMediaQualityStatusChanged(MediaQualityStatus status) { + + } } /** @@ -1029,6 +1062,32 @@ public class MmTelFeature extends ImsFeature { super.notifyCapabilitiesStatusChanged(c); } + /** + * Notify the framework that the measured media quality has crossed a threshold set by {@link + * MmTelFeature#setMediaThreshold} + * + * @param status current media quality status measured. + * @hide + */ + @SystemApi + public final void notifyMediaQualityStatusChanged( + @NonNull MediaQualityStatus status) { + if (status == null) { + throw new IllegalArgumentException( + "MediaQualityStatus must be non-null!"); + } + Log.i(LOG_TAG, "notifyMediaQualityStatusChanged " + status); + IImsMmTelListener listener = getListener(); + if (listener == null) { + throw new IllegalStateException("Session is not available."); + } + try { + listener.onMediaQualityStatusChanged(status); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + /** * Notify the framework of an incoming call. * @param c The {@link ImsCallSessionImplBase} of the new incoming call. @@ -1361,6 +1420,62 @@ public class MmTelFeature extends ImsFeature { // Base implementation, no-op } + /** + * Called by the framework to pass {@link MediaThreshold}. The MmTelFeature should override this + * method to get Media quality threshold. This will pass the consolidated threshold values from + * Telephony framework. IMS provider needs to monitor media quality of active call and notify + * media quality {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the measured + * media quality crosses at least one of {@link MediaThreshold} set by this. + * + * @param mediaSessionType media session type for this Threshold info. + * @param mediaThreshold media threshold information + * @hide + */ + @SystemApi + public void setMediaThreshold( + @MediaQualityStatus.MediaSessionType int mediaSessionType, + @NonNull MediaThreshold mediaThreshold) { + // Base Implementation - Should be overridden. + Log.d(LOG_TAG, "setMediaThreshold is not supported." + mediaThreshold); + } + + /** + * The MmTelFeature should override this method to clear Media quality thresholds that were + * registered and stop media quality status updates. + * + * @param mediaSessionType media session type + * @hide + */ + @SystemApi + public void clearMediaThreshold(@MediaQualityStatus.MediaSessionType int mediaSessionType) { + // Base Implementation - Should be overridden. + Log.d(LOG_TAG, "clearMediaThreshold is not supported." + mediaSessionType); + } + + /** + * IMS provider should override this method to return currently measured media quality status. + * + *

+ * If media quality status is not yet measured after call is active, it needs to notify media + * quality status {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the first + * measurement is done. + * + * @param mediaSessionType media session type + * @return Current media quality status. It could be null if media quality status is not + * measured yet or {@link MediaThreshold} was not set corresponding to the media session + * type. + * + * @hide + */ + @SystemApi + @Nullable + public MediaQualityStatus queryMediaQualityStatus( + @MediaQualityStatus.MediaSessionType int mediaSessionType) { + // Base Implementation - Should be overridden. + Log.d(LOG_TAG, "queryMediaQualityStatus is not supported." + mediaSessionType); + return null; + } + /** * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state. *