summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/phone/CallFeaturesSetting.java19
-rw-r--r--src/com/android/phone/CallNotifier.java74
-rw-r--r--src/com/android/phone/CarrierConfigLoader.java76
-rw-r--r--src/com/android/phone/CdmaCallOptions.java131
-rw-r--r--src/com/android/phone/CdmaCallWaitingPreference.java31
-rw-r--r--src/com/android/phone/IccPanel.java4
-rw-r--r--src/com/android/phone/ImsRcsController.java166
-rw-r--r--src/com/android/phone/LocalConnectionImpl.java38
-rw-r--r--src/com/android/phone/PhoneGlobals.java13
-rwxr-xr-xsrc/com/android/phone/PhoneInterfaceManager.java604
-rw-r--r--src/com/android/phone/RcsProvisioningMonitor.java5
-rw-r--r--src/com/android/phone/TelephonyShellCommand.java505
-rw-r--r--src/com/android/phone/callcomposer/CallComposerPictureManager.java382
-rw-r--r--src/com/android/phone/callcomposer/CallComposerPictureTransfer.java538
-rw-r--r--src/com/android/phone/callcomposer/DigestAuthUtils.java157
-rw-r--r--src/com/android/phone/callcomposer/GbaCredentials.java35
-rw-r--r--src/com/android/phone/callcomposer/GbaCredentialsSupplier.java24
-rw-r--r--src/com/android/phone/callcomposer/ImageData.java46
-rw-r--r--src/com/android/phone/euicc/EuiccUiDispatcherActivity.java7
-rw-r--r--src/com/android/phone/settings/AccessibilitySettingsFragment.java4
-rw-r--r--src/com/android/phone/settings/PhoneAccountSettingsFragment.java3
-rw-r--r--src/com/android/phone/settings/RadioInfo.java93
-rw-r--r--src/com/android/services/telephony/CdmaConferenceController.java9
-rw-r--r--src/com/android/services/telephony/DisconnectCauseUtil.java34
-rw-r--r--src/com/android/services/telephony/ImsConference.java7
-rw-r--r--src/com/android/services/telephony/PstnIncomingCallNotifier.java12
-rw-r--r--src/com/android/services/telephony/TelecomAccountRegistry.java27
-rw-r--r--[-rwxr-xr-x]src/com/android/services/telephony/TelephonyConnection.java587
-rw-r--r--src/com/android/services/telephony/TelephonyConnectionService.java260
-rw-r--r--src/com/android/services/telephony/rcs/MessageTransportWrapper.java2
-rw-r--r--src/com/android/services/telephony/rcs/SipDelegateController.java2
-rw-r--r--src/com/android/services/telephony/rcs/SipSessionTracker.java2
-rw-r--r--src/com/android/services/telephony/rcs/SipTransportController.java9
-rw-r--r--src/com/android/services/telephony/rcs/UceControllerManager.java2
34 files changed, 3314 insertions, 594 deletions
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index ae6f07268..ec6ea2bd3 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -295,7 +295,7 @@ public class CallFeaturesSetting extends PreferenceActivity
private final class CallFeaturesTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.CallStateListener {
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
+ public void onCallStateChanged(int state) {
if (DBG) log("PhoneStateListener onCallStateChanged: state is " + state);
boolean isCallStateIdle = state == TelephonyManager.CALL_STATE_IDLE;
if (mEnableVideoCalling != null) {
@@ -305,7 +305,7 @@ public class CallFeaturesSetting extends PreferenceActivity
mButtonWifiCalling.setEnabled(isCallStateIdle);
}
}
- };
+ }
private final ProvisioningManager.Callback mProvisioningCallback =
new ProvisioningManager.Callback() {
@@ -398,7 +398,8 @@ public class CallFeaturesSetting extends PreferenceActivity
cdmaOptions.setIntent(mSubscriptionInfoHelper.getIntent(CdmaCallOptions.class));
gsmOptions.setIntent(mSubscriptionInfoHelper.getIntent(GsmUmtsCallOptions.class));
} else {
- prefSet.removePreference(cdmaOptions);
+ // Remove GSM options and repopulate the preferences in this Activity if phone type is
+ // GSM.
prefSet.removePreference(gsmOptions);
int phoneType = mPhone.getPhoneType();
@@ -406,12 +407,16 @@ public class CallFeaturesSetting extends PreferenceActivity
prefSet.removePreference(fdnButton);
} else {
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ // For now, just keep CdmaCallOptions as one entity. Eventually CDMA should
+ // follow the same pattern as GSM below, where VP and Call forwarding are
+ // populated here and Call waiting is populated in another "Additional Settings"
+ // submenu for CDMA.
prefSet.removePreference(fdnButton);
- addPreferencesFromResource(R.xml.cdma_call_privacy);
- CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
- (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
- buttonVoicePrivacy.setPhone(mPhone);
+ cdmaOptions.setSummary(null);
+ cdmaOptions.setTitle(R.string.additional_gsm_call_settings);
+ cdmaOptions.setIntent(mSubscriptionInfoHelper.getIntent(CdmaCallOptions.class));
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ prefSet.removePreference(cdmaOptions);
if (mPhone.getIccCard() == null || !mPhone.getIccCard().getIccFdnAvailable()) {
prefSet.removePreference(fdnButton);
}
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 1a867b641..7f61f78cb 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -276,23 +276,10 @@ public class CallNotifier extends Handler {
private int mState;
// The possible tones we can play.
public static final int TONE_NONE = 0;
- public static final int TONE_CALL_WAITING = 1;
- public static final int TONE_BUSY = 2;
- public static final int TONE_CONGESTION = 3;
- public static final int TONE_CALL_ENDED = 4;
public static final int TONE_VOICE_PRIVACY = 5;
- public static final int TONE_REORDER = 6;
- public static final int TONE_INTERCEPT = 7;
- public static final int TONE_CDMA_DROP = 8;
- public static final int TONE_OUT_OF_SERVICE = 9;
- public static final int TONE_REDIAL = 10;
- public static final int TONE_OTA_CALL_END = 11;
- public static final int TONE_UNOBTAINABLE_NUMBER = 13;
// The tone volume relative to other sounds in the stream
- static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100;
static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
- static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
// Buffer time (in msec) to add on to tone timeout value.
// Needed mainly when the timeout value for a tone is the
@@ -320,70 +307,11 @@ public class CallNotifier extends Handler {
int phoneType = mCM.getFgPhone().getPhoneType();
switch (mToneId) {
- case TONE_CALL_WAITING:
- toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
- toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
- // Call waiting tone is stopped by stopTone() method
- toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
- break;
- case TONE_BUSY:
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT;
- toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
- toneLengthMillis = 1000;
- } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM
- || phoneType == PhoneConstants.PHONE_TYPE_SIP
- || phoneType == PhoneConstants.PHONE_TYPE_IMS
- || phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY) {
- toneType = ToneGenerator.TONE_SUP_BUSY;
- toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
- toneLengthMillis = 4000;
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
- break;
- case TONE_CONGESTION:
- toneType = ToneGenerator.TONE_SUP_CONGESTION;
- toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
- toneLengthMillis = 4000;
- break;
-
- case TONE_CALL_ENDED:
- toneType = ToneGenerator.TONE_PROP_PROMPT;
- toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
- toneLengthMillis = 200;
- break;
case TONE_VOICE_PRIVACY:
toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE;
toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 5000;
break;
- case TONE_REORDER:
- toneType = ToneGenerator.TONE_CDMA_REORDER;
- toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
- toneLengthMillis = 4000;
- break;
- case TONE_INTERCEPT:
- toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
- toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
- toneLengthMillis = 500;
- break;
- case TONE_CDMA_DROP:
- case TONE_OUT_OF_SERVICE:
- toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
- toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
- toneLengthMillis = 375;
- break;
- case TONE_REDIAL:
- toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
- toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
- toneLengthMillis = 5000;
- break;
- case TONE_UNOBTAINABLE_NUMBER:
- toneType = ToneGenerator.TONE_SUP_ERROR;
- toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
- toneLengthMillis = 4000;
- break;
default:
throw new IllegalArgumentException("Bad toneId: " + mToneId);
}
@@ -792,7 +720,7 @@ public class CallNotifier extends Handler {
mCFIStatus.put(this.mSubId, visible);
updatePhoneStateListeners(false, UPDATE_TYPE_CFI, this.mSubId);
}
- };
+ }
private void log(String msg) {
Log.d(LOG_TAG, msg);
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 577d2c0b5..d3792f1a5 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -107,10 +107,6 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
private CarrierServiceConnection[] mServiceConnection;
// Service connection for binding to carrier config app for no SIM config.
private CarrierServiceConnection[] mServiceConnectionForNoSimConfig;
- // Whether we are bound to a service for each phone
- private boolean[] mServiceBound;
- // Whether we are bound to a service for no SIM config
- private boolean[] mServiceBoundForNoSimConfig;
// Whether we have sent config change broadcast for each phone id.
private boolean[] mHasSentConfigChange;
// Whether the broadcast was sent from EVENT_SYSTEM_UNLOCKED, to track rebroadcasts
@@ -311,7 +307,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnection[phoneId] != conn || conn.service == null) {
- unbindIfBound(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
break;
}
final CarrierIdentifier carrierId = getCarrierIdentifierForPhoneId(phoneId);
@@ -320,7 +316,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
new ResultReceiver(this) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
- unbindIfBound(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT,
getMessageToken(phoneId));
// If new service connection has been created, this is stale.
@@ -355,7 +351,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
} catch (RemoteException e) {
loge("Failed to get carrier config from default app: " +
mPlatformCarrierConfigPackage + " err: " + e.toString());
- unbindIfBound(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
break; // So we don't set a timeout.
}
sendMessageDelayed(
@@ -375,7 +371,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
if (mServiceConnection[phoneId] != null) {
// If a ResponseReceiver callback is in the queue when this happens, we will
// unbind twice and throw an exception.
- unbindIfBound(mContext, mServiceConnection[phoneId], phoneId);
+ unbindIfBound(mContext, mServiceConnection[phoneId]);
broadcastConfigChangedIntent(phoneId);
}
// Put a stub bundle in place so that the rest of the logic continues smoothly.
@@ -441,7 +437,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnection[phoneId] != conn || conn.service == null) {
- unbindIfBound(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
break;
}
final CarrierIdentifier carrierId = getCarrierIdentifierForPhoneId(phoneId);
@@ -450,7 +446,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
new ResultReceiver(this) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
- unbindIfBound(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
removeMessages(EVENT_FETCH_CARRIER_TIMEOUT,
getMessageToken(phoneId));
// If new service connection has been created, this is stale.
@@ -470,7 +466,15 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
resultData.getParcelable(KEY_CONFIG_BUNDLE);
saveConfigToXml(getCarrierPackageForPhoneId(phoneId), "",
phoneId, carrierId, config);
- mConfigFromCarrierApp[phoneId] = config;
+ if (config != null) {
+ mConfigFromCarrierApp[phoneId] = config;
+ } else {
+ logdWithLocalLog("Config from carrier app is null "
+ + "for phoneId " + phoneId);
+ // Put a stub bundle in place so that the rest of the logic
+ // continues smoothly.
+ mConfigFromCarrierApp[phoneId] = new PersistableBundle();
+ }
sendMessage(
obtainMessage(
EVENT_FETCH_CARRIER_DONE, phoneId, -1));
@@ -486,7 +490,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
+ " carrierid: " + carrierId.toString());
} catch (RemoteException e) {
loge("Failed to get carrier config: " + e.toString());
- unbindIfBound(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
break; // So we don't set a timeout.
}
sendMessageDelayed(
@@ -507,7 +511,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
if (mServiceConnection[phoneId] != null) {
// If a ResponseReceiver callback is in the queue when this happens, we will
// unbind twice and throw an exception.
- unbindIfBound(mContext, mServiceConnection[phoneId], phoneId);
+ unbindIfBound(mContext, mServiceConnection[phoneId]);
broadcastConfigChangedIntent(phoneId);
}
// Put a stub bundle in place so that the rest of the logic continues smoothly.
@@ -601,8 +605,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
if (mServiceConnectionForNoSimConfig[phoneId] != null) {
// If a ResponseReceiver callback is in the queue when this happens, we will
// unbind twice and throw an exception.
- unbindIfBoundForNoSimConfig(mContext,
- mServiceConnectionForNoSimConfig[phoneId], phoneId);
+ unbindIfBound(mContext, mServiceConnectionForNoSimConfig[phoneId]);
}
broadcastConfigChangedIntent(phoneId, false);
break;
@@ -613,7 +616,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnectionForNoSimConfig[phoneId] != conn || conn.service == null) {
- unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
break;
}
@@ -622,7 +625,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
new ResultReceiver(this) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
- unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
// If new service connection has been created, this is stale.
if (mServiceConnectionForNoSimConfig[phoneId] != conn) {
loge("Received response for stale request.");
@@ -654,7 +657,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
} catch (RemoteException e) {
loge("Failed to get no sim carrier config from default app: " +
mPlatformCarrierConfigPackage + " err: " + e.toString());
- unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
+ unbindIfBound(mContext, conn);
break; // So we don't set a timeout.
}
sendMessageDelayed(
@@ -701,11 +704,9 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
mOverrideConfigs = new PersistableBundle[numPhones];
mNoSimConfig = new PersistableBundle();
mServiceConnection = new CarrierServiceConnection[numPhones];
- mServiceBound = new boolean[numPhones];
mHasSentConfigChange = new boolean[numPhones];
mFromSystemUnlocked = new boolean[numPhones];
mServiceConnectionForNoSimConfig = new CarrierServiceConnection[numPhones];
- mServiceBoundForNoSimConfig = new boolean[numPhones];
logd("CarrierConfigLoader has started");
mSubscriptionInfoUpdater = subscriptionInfoUpdater;
mHandler.sendEmptyMessage(EVENT_CHECK_SYSTEM_UPDATE);
@@ -840,11 +841,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
try {
if (mContext.bindService(carrierService, serviceConnection,
Context.BIND_AUTO_CREATE)) {
- if (eventId == EVENT_CONNECTED_TO_DEFAULT_FOR_NO_SIM_CONFIG) {
- mServiceBoundForNoSimConfig[phoneId] = true;
- } else {
- mServiceBound[phoneId] = true;
- }
+ serviceConnection.isBound = true;
return true;
} else {
return false;
@@ -884,9 +881,15 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
/** Returns the package name of a priveleged carrier app, or null if there is none. */
@Nullable
private String getCarrierPackageForPhoneId(int phoneId) {
- List<String> carrierPackageNames = TelephonyManager.from(mContext)
+ List<String> carrierPackageNames;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ carrierPackageNames = TelephonyManager.from(mContext)
.getCarrierPackageNamesForIntentAndPhone(
- new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId);
+ new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
if (carrierPackageNames != null && carrierPackageNames.size() > 0) {
return carrierPackageNames.get(0);
} else {
@@ -1355,18 +1358,9 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
return mOverrideConfigs[phoneId];
}
- private void unbindIfBound(Context context, CarrierServiceConnection conn,
- int phoneId) {
- if (mServiceBound[phoneId]) {
- mServiceBound[phoneId] = false;
- context.unbindService(conn);
- }
- }
-
- private void unbindIfBoundForNoSimConfig(Context context, CarrierServiceConnection conn,
- int phoneId) {
- if (mServiceBoundForNoSimConfig[phoneId]) {
- mServiceBoundForNoSimConfig[phoneId] = false;
+ private void unbindIfBound(Context context, CarrierServiceConnection conn) {
+ if (conn.isBound) {
+ conn.isBound = false;
context.unbindService(conn);
}
}
@@ -1591,11 +1585,15 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
final String pkgName;
final int eventId;
IBinder service;
+ // If bindService was called and return true which means unbindService
+ // must be called later to release the connection
+ boolean isBound;
CarrierServiceConnection(int phoneId, String pkgName, int eventId) {
this.phoneId = phoneId;
this.pkgName = pkgName;
this.eventId = eventId;
+ this.isBound = false;
}
@Override
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
index 2e310aab2..614587099 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -21,18 +21,36 @@ import android.os.PersistableBundle;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.feature.MmTelFeature;
+import android.util.Log;
import android.view.MenuItem;
import com.android.internal.telephony.PhoneConstants;
public class CdmaCallOptions extends TimeConsumingPreferenceActivity {
private static final String LOG_TAG = "CdmaCallOptions";
- private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static final String BUTTON_VP_KEY = "button_voice_privacy_key";
private static final String CALL_FORWARDING_KEY = "call_forwarding_key";
private static final String CALL_WAITING_KEY = "call_waiting_key";
- private CdmaVoicePrivacySwitchPreference mButtonVoicePrivacy;
+
+ private class UtCallback extends ImsMmTelManager.CapabilityCallback {
+ @Override
+ public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
+ boolean isUtAvailable = capabilities.isCapable(
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT);
+ updatePreferencesEnabled(isUtAvailable);
+ }
+ }
+
+ private Preference mCallForwardingPref;
+ private CdmaCallWaitingPreference mCallWaitingPref;
+ private UtCallback mUtCallback;
+ private ImsMmTelManager mMmTelManager;
@Override
protected void onCreate(Bundle icicle) {
@@ -44,27 +62,112 @@ public class CdmaCallOptions extends TimeConsumingPreferenceActivity {
subInfoHelper.setActionBarTitle(
getActionBar(), getResources(), R.string.labelCdmaMore_with_label);
- mButtonVoicePrivacy = (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
- mButtonVoicePrivacy.setPhone(subInfoHelper.getPhone());
+ CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
+ (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
+ buttonVoicePrivacy.setPhone(subInfoHelper.getPhone());
PersistableBundle carrierConfig;
+ int subId;
if (subInfoHelper.hasSubId()) {
- carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(
- subInfoHelper.getSubId());
+ subId = subInfoHelper.getSubId();
} else {
- carrierConfig = PhoneGlobals.getInstance().getCarrierConfig();
+ subId = SubscriptionManager.getDefaultSubscriptionId();
}
+ carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(subId);
if (subInfoHelper.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
|| carrierConfig.getBoolean(CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
- // disable the entire screen
- mButtonVoicePrivacy.setEnabled(false);
+ buttonVoicePrivacy.setEnabled(false);
+ }
+
+ mCallForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
+ if (carrierConfig != null && carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL)) {
+ mCallForwardingPref.setIntent(
+ subInfoHelper.getIntent(CdmaCallForwardOptions.class));
+ } else {
+ getPreferenceScreen().removePreference(mCallForwardingPref);
+ mCallForwardingPref = null;
+ }
+
+ mCallWaitingPref = (CdmaCallWaitingPreference) getPreferenceScreen()
+ .findPreference(CALL_WAITING_KEY);
+ if (carrierConfig == null || !carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL)) {
+ getPreferenceScreen().removePreference(mCallWaitingPref);
+ mCallWaitingPref = null;
+ }
+ // Do not go further if the preferences are removed.
+ if (mCallForwardingPref == null && mCallWaitingPref == null) return;
+
+ boolean isSsOverCdmaEnabled = carrierConfig != null && carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_SUPPORT_SS_OVER_CDMA_BOOL);
+ boolean isSsOverUtEnabled = carrierConfig != null && carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL);
+
+ if (isSsOverCdmaEnabled && mCallWaitingPref != null) {
+ // If SS over CDMA is enabled, then the preference will always be enabled,
+ // independent of SS over UT status. Initialize it now.
+ mCallWaitingPref.init(this, subInfoHelper.getPhone());
+ return;
}
+ // Since SS over UT availability can change, first disable the preferences that rely on it
+ // and only enable it if UT is available.
+ updatePreferencesEnabled(false);
+ if (isSsOverUtEnabled) {
+ // Register a callback to listen to SS over UT state. This will enable the preferences
+ // once the callback notifies settings that UT is enabled.
+ registerMmTelCapsCallback(subId);
+ } else {
+ Log.w(LOG_TAG, "SS over UT and CDMA disabled, but preferences are visible.");
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ unregisterMmTelCapsCallback();
+ }
- Preference callForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
- callForwardingPref.setIntent(subInfoHelper.getIntent(CdmaCallForwardOptions.class));
+ private void unregisterMmTelCapsCallback() {
+ if (mMmTelManager == null || mUtCallback == null) return;
+ mMmTelManager.unregisterMmTelCapabilityCallback(mUtCallback);
+ mUtCallback = null;
+ Log.d(LOG_TAG, "unregisterMmTelCapsCallback: UT availability callback unregistered");
+ }
+
+ private void registerMmTelCapsCallback(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) return;
+ ImsManager imsManager = getSystemService(ImsManager.class);
+ try {
+ if (imsManager != null) {
+ mUtCallback = new UtCallback();
+ mMmTelManager = imsManager.getImsMmTelManager(subId);
+ // Callback will call back with the state as soon as it is available.
+ mMmTelManager.registerMmTelCapabilityCallback(getMainExecutor(), mUtCallback);
+ Log.d(LOG_TAG, "registerMmTelCapsCallback: UT availability callback "
+ + "registered");
+ } else {
+ Log.w(LOG_TAG, "registerMmTelCapsCallback: couldn't get ImsManager, assuming "
+ + "UT is not available: ");
+ updatePreferencesEnabled(false);
+ }
+ } catch (IllegalArgumentException | ImsException e) {
+ Log.w(LOG_TAG, "registerMmTelCapsCallback: couldn't register callback, assuming "
+ + "UT is not available: " + e);
+ updatePreferencesEnabled(false);
+ }
+ }
+
+ private void updatePreferencesEnabled(boolean isEnabled) {
+ Log.d(LOG_TAG, "updatePreferencesEnabled: " + isEnabled);
+ if (mCallForwardingPref != null) mCallForwardingPref.setEnabled(isEnabled);
- CdmaCallWaitingPreference callWaitingPref = (CdmaCallWaitingPreference)getPreferenceScreen()
- .findPreference(CALL_WAITING_KEY);
- callWaitingPref.init(this, subInfoHelper.getPhone());
+ if (mCallWaitingPref == null || mCallWaitingPref.isEnabled() == isEnabled) return;
+ mCallWaitingPref.setActionAvailable(isEnabled);
+ if (isEnabled) {
+ SubscriptionInfoHelper subInfoHelper = new SubscriptionInfoHelper(this, getIntent());
+ // kick off the normal process to populate the Call Waiting status.
+ mCallWaitingPref.init(this, subInfoHelper.getPhone());
+ }
}
@Override
diff --git a/src/com/android/phone/CdmaCallWaitingPreference.java b/src/com/android/phone/CdmaCallWaitingPreference.java
index 4cda7ba59..3713b1939 100644
--- a/src/com/android/phone/CdmaCallWaitingPreference.java
+++ b/src/com/android/phone/CdmaCallWaitingPreference.java
@@ -16,32 +16,29 @@
package com.android.phone;
-import com.android.internal.telephony.CommandException;
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.Phone;
-
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.res.TypedArray;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.util.AttributeSet;
import android.util.Log;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
public class CdmaCallWaitingPreference extends Preference {
private static final String LOG_TAG = "CdmaCallWaitingPreference";
private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
- private int mButtonClicked;
private Context mContext;
private Phone mPhone;
- private SubscriptionInfoHelper mSubscriptionInfoHelper;
private TimeConsumingPreferenceListener mTcpListener;
private MyHandler mHandler = new MyHandler();
+ private boolean mIsActionAvailable = true;
public CdmaCallWaitingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -67,6 +64,16 @@ public class CdmaCallWaitingPreference extends Preference {
}
}
+ /**
+ * Enables this preference if Call waiting is available in the platform. If not, this will
+ * override all attempts to enable the preference from the associated
+ * TimeConsumingPreferenceActivity.
+ */
+ public void setActionAvailable(boolean isAvailable) {
+ mIsActionAvailable = isAvailable;
+ super.setEnabled(mIsActionAvailable);
+ }
+
@Override
public void onClick() {
super.onClick();
@@ -95,6 +102,14 @@ public class CdmaCallWaitingPreference extends Preference {
builder.create().show();
}
+ @Override
+ public void setEnabled(boolean enabled) {
+ // If this action is currently disabled due to configuration changes, do not allow anything
+ // to enable it.
+ if (!mIsActionAvailable) return;
+ super.setEnabled(enabled);
+ }
+
private class MyHandler extends Handler {
static final int MESSAGE_GET_CALL_WAITING = 0;
static final int MESSAGE_SET_CALL_WAITING = 1;
diff --git a/src/com/android/phone/IccPanel.java b/src/com/android/phone/IccPanel.java
index 73dd8bc4e..4018e4050 100644
--- a/src/com/android/phone/IccPanel.java
+++ b/src/com/android/phone/IccPanel.java
@@ -74,13 +74,13 @@ public class IccPanel extends Dialog {
@Override
protected void onStart() {
super.onStart();
- mStatusBarManager.setDisabledForSimNetworkLock(true);
+ mStatusBarManager.setExpansionDisabledForSimNetworkLock(true);
}
@Override
public void onStop() {
super.onStop();
- mStatusBarManager.setDisabledForSimNetworkLock(false);
+ mStatusBarManager.setExpansionDisabledForSimNetworkLock(false);
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 2e4ee94d4..7d594d12f 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -49,7 +49,6 @@ import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.ims.ImsResolver;
-import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.services.telephony.rcs.RcsFeatureController;
import com.android.services.telephony.rcs.SipTransportController;
import com.android.services.telephony.rcs.TelephonyRcsService;
@@ -214,6 +213,8 @@ public class ImsRcsController extends IImsRcsController.Stub {
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).unregisterRcsAvailabilityCallback(subId, callback);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "unregisterRcsAvailabilityCallback: error=" + e.errorCode);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -338,12 +339,16 @@ public class ImsRcsController extends IImsRcsController.Stub {
public RcsContactUceCapability addUceRegistrationOverrideShell(int subId,
Set<String> featureTags) throws ImsException {
// Permission check happening in PhoneInterfaceManager.
- UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
- UceControllerManager.class);
- if (uceCtrlManager == null) {
- return null;
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.addUceRegistrationOverride(featureTags);
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
}
- return uceCtrlManager.addUceRegistrationOverride(featureTags);
}
/**
@@ -353,12 +358,16 @@ public class ImsRcsController extends IImsRcsController.Stub {
public RcsContactUceCapability removeUceRegistrationOverrideShell(int subId,
Set<String> featureTags) throws ImsException {
// Permission check happening in PhoneInterfaceManager.
- UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
- UceControllerManager.class);
- if (uceCtrlManager == null) {
- return null;
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.removeUceRegistrationOverride(featureTags);
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
}
- return uceCtrlManager.removeUceRegistrationOverride(featureTags);
}
/**
@@ -367,13 +376,17 @@ public class ImsRcsController extends IImsRcsController.Stub {
// Used for SHELL command only right now.
public RcsContactUceCapability clearUceRegistrationOverrideShell(int subId)
throws ImsException {
- // Permission check happening in PhoneInterfaceManager.
- UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
- UceControllerManager.class);
- if (uceCtrlManager == null) {
- return null;
+ try {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.clearUceRegistrationOverride();
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
}
- return uceCtrlManager.clearUceRegistrationOverride();
}
/**
@@ -382,13 +395,17 @@ public class ImsRcsController extends IImsRcsController.Stub {
// Used for SHELL command only right now.
public RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId)
throws ImsException {
- // Permission check happening in PhoneInterfaceManager.
- UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
- UceControllerManager.class);
- if (uceCtrlManager == null) {
- return null;
+ try {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.getLatestRcsContactUceCapability();
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
}
- return uceCtrlManager.getLatestRcsContactUceCapability();
}
/**
@@ -397,14 +414,18 @@ public class ImsRcsController extends IImsRcsController.Stub {
*/
// Used for SHELL command only right now.
public String getLastUcePidfXmlShell(int subId) throws ImsException {
- // Permission check happening in PhoneInterfaceManager.
- UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
- UceControllerManager.class);
- if (uceCtrlManager == null) {
- return null;
+ try {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ String pidfXml = uceCtrlManager.getLastPidfXml();
+ return pidfXml == null ? "none" : pidfXml;
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
}
- String pidfXml = uceCtrlManager.getLastPidfXml();
- return pidfXml == null ? "none" : pidfXml;
}
/**
@@ -413,12 +434,16 @@ public class ImsRcsController extends IImsRcsController.Stub {
*/
// Used for SHELL command only right now.
public boolean removeUceRequestDisallowedStatus(int subId) throws ImsException {
- UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
- UceControllerManager.class);
- if (uceCtrlManager == null) {
- return false;
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId, true).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return false;
+ }
+ return uceCtrlManager.removeUceRequestDisallowedStatus();
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
}
- return uceCtrlManager.removeUceRequestDisallowedStatus();
}
/**
@@ -426,12 +451,16 @@ public class ImsRcsController extends IImsRcsController.Stub {
*/
// Used for SHELL command only right now.
public boolean setCapabilitiesRequestTimeout(int subId, long timeoutAfter) throws ImsException {
- UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
- UceControllerManager.class);
- if (uceCtrlManager == null) {
- return false;
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId, true).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return false;
+ }
+ return uceCtrlManager.setCapabilitiesRequestTimeout(timeoutAfter);
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
}
- return uceCtrlManager.setCapabilitiesRequestTimeout(timeoutAfter);
}
@Override
@@ -465,6 +494,8 @@ public class ImsRcsController extends IImsRcsController.Stub {
"This subscription does not support UCE.");
}
uceCtrlManager.unregisterPublishStateCallback(c);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "unregisterUcePublishStateCallback: error=" + e.errorCode);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -570,8 +601,11 @@ public class ImsRcsController extends IImsRcsController.Stub {
@Override
public void destroySipDelegate(int subId, ISipDelegate connection, int reason) {
- enforceImsSingleRegistrationPermission("destroySipDelegate");
-
+ // Do not check permissions here - the caller needs to have a connection already from the
+ // create method to call this method.
+ if (connection == null) {
+ return;
+ }
final long identity = Binder.clearCallingIdentity();
try {
SipTransportController transport = getRcsFeatureController(subId).getFeature(
@@ -580,6 +614,8 @@ public class ImsRcsController extends IImsRcsController.Stub {
return;
}
transport.destroySipDelegate(subId, connection, reason);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "destroySipDelegate: error=" + e.errorCode);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -598,6 +634,8 @@ public class ImsRcsController extends IImsRcsController.Stub {
return;
}
transport.triggerFullNetworkRegistration(subId, connection, sipCode, sipReason);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "triggerNetworkRegistration: error=" + e.errorCode);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -688,38 +726,25 @@ public class ImsRcsController extends IImsRcsController.Stub {
}
/**
- * Retrieve ImsPhone instance.
+ * Retrieve RcsFeatureManager instance.
*
* @param subId the subscription ID
- * @return The ImsPhone instance
- * @throws ServiceSpecificException if getting ImsPhone instance failed.
+ * @return The RcsFeatureManager instance
+ * @throws ServiceSpecificException if getting RcsFeatureManager instance failed.
*/
- private ImsPhone getImsPhone(int subId) {
- if (!ImsManager.isImsSupportedOnDevice(mApp)) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
- "IMS is not available on device.");
- }
- Phone phone = PhoneGlobals.getPhone(subId);
- if (phone == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
- "Invalid subscription Id: " + subId);
- }
- ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
- if (imsPhone == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
- "Cannot find ImsPhone instance: " + subId);
- }
- return imsPhone;
+ private RcsFeatureController getRcsFeatureController(int subId) {
+ return getRcsFeatureController(subId, false /* skipVerifyingConfig */);
}
/**
* Retrieve RcsFeatureManager instance.
*
* @param subId the subscription ID
+ * @param skipVerifyingConfig If the RCS configuration can be skip.
* @return The RcsFeatureManager instance
* @throws ServiceSpecificException if getting RcsFeatureManager instance failed.
*/
- private RcsFeatureController getRcsFeatureController(int subId) {
+ private RcsFeatureController getRcsFeatureController(int subId, boolean skipVerifyingConfig) {
if (!ImsManager.isImsSupportedOnDevice(mApp)) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"IMS is not available on device.");
@@ -734,6 +759,9 @@ public class ImsRcsController extends IImsRcsController.Stub {
"Invalid subscription Id: " + subId);
}
int slotId = phone.getPhoneId();
+ if (!skipVerifyingConfig) {
+ verifyImsRcsConfiguredOrThrow(slotId);
+ }
RcsFeatureController c = mRcsService.getFeatureController(slotId);
if (c == null) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
@@ -742,6 +770,20 @@ public class ImsRcsController extends IImsRcsController.Stub {
return c;
}
+ /**
+ * Throw an ImsException if the IMS resolver does not have an ImsService configured for RCS
+ * for the given slot ID or no ImsResolver instance has been created.
+ * @param slotId The slot ID that the IMS service is created for.
+ * @throws ServiceSpecificException If there is no ImsService configured for this slot.
+ */
+ private void verifyImsRcsConfiguredOrThrow(int slotId) {
+ if (mImsResolver == null
+ || !mImsResolver.isImsServiceConfiguredForFeature(slotId, ImsFeature.FEATURE_RCS)) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support RCS");
+ }
+ }
+
private boolean isImsSingleRegistrationSupportedOnDevice() {
return mSingleRegistrationOverride != null ? mSingleRegistrationOverride
: mApp.getPackageManager().hasSystemFeature(
diff --git a/src/com/android/phone/LocalConnectionImpl.java b/src/com/android/phone/LocalConnectionImpl.java
new file mode 100644
index 000000000..c2630ef78
--- /dev/null
+++ b/src/com/android/phone/LocalConnectionImpl.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.phone;
+
+import android.content.Context;
+import android.telephony.TelephonyLocalConnection;
+
+import com.android.phone.callcomposer.CallComposerPictureManager;
+
+import java.util.UUID;
+
+public class LocalConnectionImpl implements TelephonyLocalConnection.ConnectionImpl {
+ private Context mContext;
+
+ public LocalConnectionImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public String getCallComposerServerUrlForHandle(int subscriptionId, UUID uuid) {
+ return CallComposerPictureManager.getInstance(mContext, subscriptionId)
+ .getServerUrlForImageId(uuid);
+ }
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index c4686db4c..1a1c32195 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -49,6 +49,7 @@ import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyLocalConnection;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.util.LocalLog;
@@ -56,6 +57,7 @@ import android.util.Log;
import android.widget.Toast;
import com.android.ims.ImsFeatureBinderRepository;
+import com.android.internal.os.BinderCallsStats;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.MmiCode;
@@ -208,6 +210,7 @@ public class PhoneGlobals extends ContextWrapper {
new CarrierVvmPackageInstalledReceiver();
private final SettingsObserver mSettingsObserver;
+ private BinderCallsStats.SettingsObserver mBinderCallsSettingsObserver;
private static class EventSimStateChangedBag {
final int mPhoneId;
@@ -372,6 +375,9 @@ public class PhoneGlobals extends ContextWrapper {
ContentResolver resolver = getContentResolver();
+ // Initialize the shim from frameworks/opt/telephony into packages/services/Telephony.
+ TelephonyLocalConnection.setInstance(new LocalConnectionImpl(this));
+
// Cache the "voice capable" flag.
// This flag currently comes from a resource (which is
// overrideable on a per-product basis):
@@ -523,6 +529,13 @@ public class PhoneGlobals extends ContextWrapper {
SettingsConstants.HAC_KEY + "=" + (hac == SettingsConstants.HAC_ENABLED
? SettingsConstants.HAC_VAL_ON : SettingsConstants.HAC_VAL_OFF));
}
+
+ // Start tracking Binder latency for the phone process.
+ mBinderCallsSettingsObserver = new BinderCallsStats.SettingsObserver(
+ getApplicationContext(),
+ new BinderCallsStats(
+ new BinderCallsStats.Injector(),
+ com.android.internal.os.BinderLatencyProto.Dims.TELEPHONY));
}
/**
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index f0a6a69c5..db14924f1 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -29,6 +29,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -106,7 +108,7 @@ import android.telephony.UiccSlotInfo;
import android.telephony.UssdResponse;
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.telephony.data.ApnSetting;
-import android.telephony.data.SlicingConfig;
+import android.telephony.data.NetworkSlicingConfig;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.gba.GbaAuthRequest;
import android.telephony.gba.UaSecurityProtocolIdentifier;
@@ -139,7 +141,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.CarrierInfoManager;
+import com.android.internal.telephony.CallTracker;
import com.android.internal.telephony.CarrierResolver;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.CommandException;
@@ -193,6 +195,9 @@ import com.android.internal.telephony.util.LocaleUtils;
import com.android.internal.telephony.util.VoicemailNotificationSettingsUtil;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.HexDump;
+import com.android.phone.callcomposer.CallComposerPictureManager;
+import com.android.phone.callcomposer.CallComposerPictureTransfer;
+import com.android.phone.callcomposer.ImageData;
import com.android.phone.settings.PickSmsSubscriptionActivity;
import com.android.phone.vvm.PhoneAccountHandleConverter;
import com.android.phone.vvm.RemoteVvmTaskManager;
@@ -202,7 +207,10 @@ import com.android.services.telephony.TelecomAccountRegistry;
import com.android.services.telephony.TelephonyConnectionService;
import com.android.telephony.Rlog;
+import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -332,6 +340,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
private static final int CMD_PREPARE_UNATTENDED_REBOOT = 109;
private static final int CMD_GET_SLICING_CONFIG = 110;
private static final int EVENT_GET_SLICING_CONFIG_DONE = 111;
+ private static final int CMD_ERASE_DATA_SHARED_PREFERENCES = 112;
// Parameters of select command.
private static final int SELECT_COMMAND = 0xA4;
@@ -348,6 +357,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
private ImsResolver mImsResolver;
private UserManager mUserManager;
private AppOpsManager mAppOps;
+ private PackageManager mPm;
private MainThreadHandler mMainThreadHandler;
private SubscriptionController mSubscriptionController;
private SharedPreferences mTelephonySharedPreferences;
@@ -1701,6 +1711,12 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
handleNullReturnEvent(msg, "eraseModemConfig");
break;
+ case CMD_ERASE_DATA_SHARED_PREFERENCES:
+ request = (MainThreadRequest) msg.obj;
+ request.result = defaultPhone.eraseDataInSharedPreferences();
+ notifyRequester(request);
+ break;
+
case CMD_CHANGE_ICC_LOCK_PASSWORD:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_CHANGE_ICC_LOCK_PASSWORD_DONE, request);
@@ -1872,7 +1888,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
(Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
onCompleted = obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
request);
- phone.getServiceStateTracker().setSignalStrengthUpdateRequest(
+ phone.getSignalStrengthController().setSignalStrengthUpdateRequest(
request.subId, pair.first /*callingUid*/,
pair.second /*request*/, onCompleted);
break;
@@ -1900,7 +1916,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
(Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
onCompleted = obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
request);
- phone.getServiceStateTracker().clearSignalStrengthUpdateRequest(
+ phone.getSignalStrengthController().clearSignalStrengthUpdateRequest(
request.subId, pair.first /*callingUid*/,
pair.second /*request*/, onCompleted);
break;
@@ -1924,24 +1940,24 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
request = (MainThreadRequest) ar.userObj;
ResultReceiver result = (ResultReceiver) request.argument;
- SlicingConfig slicingConfig = null;
+ NetworkSlicingConfig slicingConfig = null;
Bundle bundle = new Bundle();
int resultCode = 0;
if (ar.exception != null) {
Log.e(LOG_TAG, "Exception retrieving slicing configuration="
+ ar.exception);
- resultCode = TelephonyManager.SlicingException.ERROR_MODEM_ERROR;
+ resultCode = TelephonyManager.NetworkSlicingException.ERROR_MODEM_ERROR;
} else if (ar.result == null) {
Log.w(LOG_TAG, "Timeout Waiting for slicing configuration!");
- resultCode = TelephonyManager.SlicingException.ERROR_TIMEOUT;
+ resultCode = TelephonyManager.NetworkSlicingException.ERROR_TIMEOUT;
} else {
// use the result as returned
- resultCode = TelephonyManager.SlicingException.SUCCESS;
- slicingConfig = (SlicingConfig) ar.result;
+ resultCode = TelephonyManager.NetworkSlicingException.SUCCESS;
+ slicingConfig = (NetworkSlicingConfig) ar.result;
}
if (slicingConfig == null) {
- slicingConfig = new SlicingConfig();
+ slicingConfig = new NetworkSlicingConfig();
}
bundle.putParcelable(TelephonyManager.KEY_SLICING_CONFIG_HANDLE, slicingConfig);
result.send(resultCode, bundle);
@@ -2074,7 +2090,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
// Wait for at least timeoutInMs before returning null request result
long now = SystemClock.elapsedRealtime();
long deadline = now + timeoutInMs;
- while (request == null && now < deadline) {
+ while (request.result == null && now < deadline) {
try {
request.wait(deadline - now);
} catch (InterruptedException e) {
@@ -2152,6 +2168,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
mImsResolver = ImsResolver.getInstance();
mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
+ mPm = app.getSystemService(PackageManager.class);
mMainThreadHandler = new MainThreadHandler();
mSubscriptionController = SubscriptionController.getInstance();
mTelephonySharedPreferences =
@@ -2202,18 +2219,14 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
return PhoneFactory.getPhone(mSubscriptionController.getPhoneId(subId));
}
- private void sendEraseModemConfig(Phone phone) {
- if (phone != null) {
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
- mApp, phone.getSubId(), "eraseModemConfig");
- final long identity = Binder.clearCallingIdentity();
- try {
- Boolean success = (Boolean) sendRequest(CMD_ERASE_MODEM_CONFIG, null);
- if (DBG) log("eraseModemConfig:" + ' ' + (success ? "ok" : "fail"));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
+ private void sendEraseModemConfig(@NonNull Phone phone) {
+ Boolean success = (Boolean) sendRequest(CMD_ERASE_MODEM_CONFIG, null);
+ if (DBG) log("eraseModemConfig:" + ' ' + (success ? "ok" : "fail"));
+ }
+
+ private void sendEraseDataInSharedPreferences(@NonNull Phone phone) {
+ Boolean success = (Boolean) sendRequest(CMD_ERASE_DATA_SHARED_PREFERENCES, null);
+ if (DBG) log("eraseDataInSharedPreferences:" + ' ' + (success ? "ok" : "fail"));
}
private boolean isImsAvailableOnDevice() {
@@ -2758,14 +2771,46 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
}
+ /**
+ * @deprecated This method is deprecated and is only being kept due to an UnsupportedAppUsage
+ * tag on getCallState Binder call.
+ */
+ @Deprecated
+ @Override
public int getCallState() {
- return getCallStateForSlot(getSlotForDefaultSubscription());
+ if (CompatChanges.isChangeEnabled(
+ TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION,
+ Binder.getCallingUid())) {
+ // Do not allow this API to be called on API version 31+, it should only be
+ // called on old apps using this Binder call directly.
+ throw new SecurityException("This method can only be used for applications "
+ + "targeting API version 30 or less.");
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Phone phone = getPhone(getDefaultSubscription());
+ return phone == null ? TelephonyManager.CALL_STATE_IDLE :
+ PhoneConstantConversions.convertCallState(phone.getState());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
- public int getCallStateForSlot(int slotIndex) {
+ @Override
+ public int getCallStateForSubscription(int subId, String callingPackage, String featureId) {
+ if (CompatChanges.isChangeEnabled(
+ TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION,
+ Binder.getCallingUid())) {
+ // Check READ_PHONE_STATE for API version 31+
+ if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mApp, subId, callingPackage,
+ featureId, "getCallStateForSubscription")) {
+ throw new SecurityException("getCallState requires READ_PHONE_STATE for apps "
+ + "targeting API level 31+.");
+ }
+ }
final long identity = Binder.clearCallingIdentity();
try {
- Phone phone = PhoneFactory.getPhone(slotIndex);
+ Phone phone = getPhone(subId);
return phone == null ? TelephonyManager.CALL_STATE_IDLE :
PhoneConstantConversions.convertCallState(phone.getState());
} finally {
@@ -3061,6 +3106,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
return null;
}
int subId = phone.getSubId();
+ enforceCallingPackage(callingPackage, Binder.getCallingUid(), "getImeiForSlot");
if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mApp, subId,
callingPackage, callingFeatureId, "getImeiForSlot")) {
return null;
@@ -3080,7 +3126,12 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
String tac = null;
if (phone != null) {
String imei = phone.getImei();
- tac = imei == null ? null : imei.substring(0, TYPE_ALLOCATION_CODE_LENGTH);
+ try {
+ tac = imei == null ? null : imei.substring(0, TYPE_ALLOCATION_CODE_LENGTH);
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(LOG_TAG, "IMEI length shorter than upper index.");
+ return null;
+ }
}
return tac;
}
@@ -3112,7 +3163,13 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
String manufacturerCode = null;
if (phone != null) {
String meid = phone.getMeid();
- manufacturerCode = meid == null ? null : meid.substring(0, MANUFACTURER_CODE_LENGTH);
+ try {
+ manufacturerCode =
+ meid == null ? null : meid.substring(0, MANUFACTURER_CODE_LENGTH);
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(LOG_TAG, "MEID length shorter than upper index.");
+ return null;
+ }
}
return manufacturerCode;
}
@@ -3206,6 +3263,26 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
//
/**
+ * Make sure the caller is the calling package itself
+ *
+ * @throws SecurityException if the caller is not the calling package
+ */
+ private void enforceCallingPackage(String callingPackage, int callingUid, String message) {
+ int packageUid = -1;
+ PackageManager pm = mApp.getBaseContext().createContextAsUser(
+ UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+ try {
+ packageUid = pm.getPackageUid(callingPackage, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ // packageUid is -1
+ }
+ if (packageUid != callingUid) {
+ throw new SecurityException(message + ": Package " + callingPackage
+ + " does not belong to " + callingUid);
+ }
+ }
+
+ /**
* Make sure the caller has the MODIFY_PHONE_STATE permission.
*
* @throws SecurityException if the caller does not have the required permission
@@ -3780,9 +3857,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
final long token = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
- .addRegistrationCallbackForSubscription(c, subId);
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ ImsManager.getInstance(mApp, slotId).addRegistrationCallbackForSubscription(c, subId);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3804,7 +3881,6 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
final long token = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone.
ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
.removeRegistrationCallbackForSubscription(c, subId);
} catch (ImsException e) {
@@ -3900,11 +3976,11 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"IMS not available on device.");
}
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
final long token = Binder.clearCallingIdentity();
try {
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
- .addCapabilitiesCallbackForSubscription(c, subId);
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ ImsManager.getInstance(mApp, slotId).addCapabilitiesCallbackForSubscription(c, subId);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3927,7 +4003,6 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
final long token = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone.
ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
.removeCapabilitiesCallbackForSubscription(c, subId);
} catch (ImsException e) {
@@ -3943,11 +4018,11 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public boolean isCapable(int subId, int capability, int regTech) {
enforceReadPrivilegedPermission("isCapable");
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
final long token = Binder.clearCallingIdentity();
try {
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).queryMmTelCapability(capability, regTech);
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ return ImsManager.getInstance(mApp, slotId).queryMmTelCapability(capability, regTech);
} catch (com.android.ims.ImsException e) {
Log.w(LOG_TAG, "IMS isCapable - service unavailable: " + e.getMessage());
return false;
@@ -3999,6 +4074,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
+ subId + "'");
throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
}
+ verifyImsMmTelConfiguredOrThrow(slotId);
ImsManager.getInstance(mApp, slotId).isSupported(capability,
transportType, aBoolean -> {
try {
@@ -4008,6 +4084,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
+ "running. Ignore");
}
});
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -4022,11 +4100,11 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isAdvancedCallingSettingEnabled");
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
final long token = Binder.clearCallingIdentity();
try {
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isEnhanced4gLteModeSettingEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isEnhanced4gLteModeSettingEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4040,9 +4118,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setAdvancedCallingSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setEnhanced4gLteModeSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setEnhanced4gLteModeSetting(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4060,8 +4139,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
mApp, subId, "isVtSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).isVtEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isVtEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4075,8 +4155,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setVtSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setVtSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setVtSetting(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4094,9 +4176,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
mApp, subId, "isVoWiFiSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isWfcEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isWfcEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4110,8 +4192,55 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setVoWiFiSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setWfcSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcSetting(isEnabled);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * @return true if the user's setting for Voice over Cross SIM is enabled and false if it is not
+ * Requires carrier privileges or READ_PRECISE_PHONE_STATE permission.
+ * @param subId The subscription to use to check the configuration.
+ */
+ @Override
+ public boolean isCrossSimCallingEnabledByUser(int subId) {
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "isCrossSimCallingEnabledByUser");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isCrossSimCallingEnabledByUser();
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Sets the user's setting for whether or not Voice over Cross SIM is enabled.
+ * Requires MODIFY_PHONE_STATE permission.
+ * @param subId The subscription to use to check the configuration.
+ * @param isEnabled true if the user's setting for Voice over Cross SIM is enabled,
+ * false otherwise
+ */
+ @Override
+ public void setCrossSimCallingEnabled(int subId, boolean isEnabled) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
+ "setCrossSimCallingEnabled");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setCrossSimCallingEnabled(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4124,14 +4253,15 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
* @param subId The subscription to use to check the configuration.
*/
@Override
+
public boolean isVoWiFiRoamingSettingEnabled(int subId) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVoWiFiRoamingSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isWfcRoamingEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isWfcRoamingEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4145,9 +4275,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setVoWiFiRoamingSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcRoamingSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcRoamingSetting(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4161,9 +4292,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setVoWiFiNonPersistent");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcNonPersistent(isCapable, mode);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting will be ignored if the ImsService isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcNonPersistent(isCapable, mode);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4181,9 +4312,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
mApp, subId, "getVoWiFiModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).getWfcMode(false /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).getWfcMode(false /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4197,9 +4328,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setVoWiFiModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcMode(mode, false /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcMode(mode, false /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4212,9 +4344,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
enforceReadPrivilegedPermission("getVoWiFiRoamingModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).getWfcMode(true /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).getWfcMode(true /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4228,9 +4360,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setVoWiFiRoamingModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcMode(mode, true /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcMode(mode, true /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4244,8 +4377,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
"setRttCapabilityEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setRttEnabled(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setRttEnabled(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4263,9 +4398,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
mApp, subId, "isTtyOverVolteEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isTtyOnVoLteCapable();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isTtyOnVoLteCapable();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -4282,8 +4417,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"IMS not available on device.");
}
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ ImsManager.getInstance(mApp, slotId)
.addProvisioningCallbackForSubscription(callback, subId);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
@@ -4300,7 +4436,6 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
.removeProvisioningCallbackForSubscription(callback, subId);
} catch (ImsException e) {
@@ -4401,8 +4536,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
boolean isProvisioned) {
- if (tech < ImsRegistrationImplBase.REGISTRATION_TECH_LTE
- || tech > ImsRegistrationImplBase.REGISTRATION_TECH_NR) {
+ if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
}
checkModifyPhoneStatePermission(subId, "setImsProvisioningStatusForCapability");
@@ -4412,8 +4549,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
if (!isImsProvisioningRequired(subId, capability, true)) {
return;
}
- if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
- && tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) {
+ if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ || tech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
loge("setImsProvisioningStatusForCapability: called for technology that does "
+ "not support provisioning - " + tech);
return;
@@ -4466,8 +4603,10 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech) {
- if (tech < ImsRegistrationImplBase.REGISTRATION_TECH_LTE
- || tech > ImsRegistrationImplBase.REGISTRATION_TECH_NR) {
+ if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
}
enforceReadPrivilegedPermission("getProvisioningStatusForCapability");
@@ -4478,8 +4617,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
return true;
}
- if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
- && tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) {
+ if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ || tech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
loge("getImsProvisioningStatusForCapability: called for technology that does "
+ "not support provisioning - " + tech);
return true;
@@ -4743,6 +4882,20 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
}
+ /**
+ * Throw an ImsException if the IMS resolver does not have an ImsService configured for MMTEL
+ * for the given slot ID or no ImsResolver instance has been created.
+ * @param slotId The slot ID that the IMS service is created for.
+ * @throws ImsException If there is no ImsService configured for this slot.
+ */
+ private void verifyImsMmTelConfiguredOrThrow(int slotId) throws ImsException {
+ if (mImsResolver == null || !mImsResolver.isImsServiceConfiguredForFeature(slotId,
+ ImsFeature.FEATURE_MMTEL)) {
+ throw new ImsException("This subscription does not support MMTEL over IMS",
+ ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+ }
+ }
+
private int getSlotIndexOrException(int subId) throws ImsException {
int slotId = SubscriptionManager.getSlotIndex(subId);
if (!SubscriptionManager.isValidSlotIndex(slotId)) {
@@ -5703,6 +5856,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
+ subId + "'");
throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
}
+ verifyImsMmTelConfiguredOrThrow(slotId);
ImsManager.getInstance(mApp, slotId).getImsServiceState(anInteger -> {
try {
callback.accept(anInteger == null ? ImsFeature.STATE_UNAVAILABLE : anInteger);
@@ -5711,6 +5865,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
+ "Ignore");
}
});
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -5807,12 +5963,12 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
- return "";
+ throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
}
final Phone phone = getPhone(subId);
if (phone == null) {
- return "";
+ throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
}
OperatorInfo networkSelection = phone.getSavedNetworkSelection();
return TextUtils.isEmpty(networkSelection.getOperatorNumeric())
@@ -6076,6 +6232,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
.setCallingUid(Binder.getCallingUid())
.setMethod("requestNetworkScan")
.setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
.build());
if (locationResult != LocationAccessPolicy.LocationPermissionResult.ALLOWED) {
SecurityException e = checkNetworkRequestForSanitizedLocationAccess(
@@ -6589,6 +6747,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public int checkCarrierPrivilegesForPackage(int subId, String pkgName) {
+ enforceReadPrivilegedPermission("checkCarrierPrivilegesForPackage");
if (TextUtils.isEmpty(pkgName)) {
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
}
@@ -6606,6 +6765,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public int checkCarrierPrivilegesForPackageAnyPhone(String pkgName) {
+ // TODO(b/186774706): Remove @RequiresPermission from TelephonyManager API
if (TextUtils.isEmpty(pkgName))
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
int result = TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
@@ -6629,6 +6789,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public List<String> getCarrierPackageNamesForIntentAndPhone(Intent intent, int phoneId) {
+ enforceReadPrivilegedPermission("getCarrierPackageNamesForIntentAndPhone");
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
loge("phoneId " + phoneId + " is not valid.");
return null;
@@ -6643,6 +6804,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public List<String> getPackagesWithCarrierPrivileges(int phoneId) {
+ enforceReadPrivilegedPermission("getPackagesWithCarrierPrivileges");
PackageManager pm = mApp.getPackageManager();
List<String> privilegedPackages = new ArrayList<>();
List<PackageInfo> packages = null;
@@ -6661,7 +6823,9 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
for (int p = packages.size() - 1; p >= 0; p--) {
PackageInfo pkgInfo = packages.get(p);
if (pkgInfo != null && pkgInfo.packageName != null
- && card.getCarrierPrivilegeStatus(pkgInfo)
+ && getCarrierPrivilegeStatusFromCarrierConfigRules(
+ card.getCarrierPrivilegeStatus(pkgInfo),
+ getPhone(phoneId), pkgInfo.packageName)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
privilegedPackages.add(pkgInfo.packageName);
}
@@ -7068,6 +7232,98 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
@Override
+ public void uploadCallComposerPicture(int subscriptionId, String callingPackage,
+ String contentType, ParcelFileDescriptor fd, ResultReceiver callback) {
+ try {
+ if (!Objects.equals(mApp.getPackageManager().getPackageUid(callingPackage, 0),
+ Binder.getCallingUid())) {
+ throw new SecurityException("Invalid package:" + callingPackage);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new SecurityException("Invalid package:" + callingPackage);
+ }
+ RoleManager rm = mApp.getSystemService(RoleManager.class);
+ List<String> dialerRoleHolders = rm.getRoleHolders(RoleManager.ROLE_DIALER);
+ if (!dialerRoleHolders.contains(callingPackage)) {
+ throw new SecurityException("App must be the dialer role holder to"
+ + " upload a call composer pic");
+ }
+
+ Executors.newSingleThreadExecutor().execute(() -> {
+ ByteArrayOutputStream output = new ByteArrayOutputStream(
+ (int) TelephonyManager.getMaximumCallComposerPictureSize());
+ InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ boolean readUntilEnd = false;
+ int totalBytesRead = 0;
+ byte[] buffer = new byte[16 * 1024];
+ while (true) {
+ int numRead;
+ try {
+ numRead = input.read(buffer);
+ } catch (IOException e) {
+ try {
+ fd.checkError();
+ callback.send(TelephonyManager.CallComposerException.ERROR_INPUT_CLOSED,
+ null);
+ } catch (IOException e1) {
+ // This means that the other side closed explicitly with an error. If this
+ // happens, log and ignore.
+ loge("Remote end of call composer picture pipe closed: " + e1);
+ }
+ break;
+ }
+ if (numRead == -1) {
+ readUntilEnd = true;
+ break;
+ }
+ totalBytesRead += numRead;
+ if (totalBytesRead > TelephonyManager.getMaximumCallComposerPictureSize()) {
+ loge("Too many bytes read for call composer picture: " + totalBytesRead);
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ break;
+ }
+ output.write(buffer, 0, numRead);
+ }
+ // Generally, the remote end will close the file descriptors. The only case where we
+ // close is above, where the picture size is too big.
+
+ try {
+ fd.checkError();
+ } catch (IOException e) {
+ loge("Remote end for call composer closed with an error: " + e);
+ return;
+ }
+
+ if (!readUntilEnd) {
+ loge("Did not finish reading entire image; aborting");
+ return;
+ }
+
+ ImageData imageData = new ImageData(output.toByteArray(), contentType, null);
+ CallComposerPictureManager.getInstance(mApp, subscriptionId).handleUploadToServer(
+ new CallComposerPictureTransfer.Factory() {},
+ imageData,
+ (result) -> {
+ if (result.first != null) {
+ ParcelUuid parcelUuid = new ParcelUuid(result.first);
+ Bundle outputResult = new Bundle();
+ outputResult.putParcelable(
+ TelephonyManager.KEY_CALL_COMPOSER_PICTURE_HANDLE, parcelUuid);
+ callback.send(TelephonyManager.CallComposerException.SUCCESS,
+ outputResult);
+ } else {
+ callback.send(result.second, null);
+ }
+ }
+ );
+ });
+ }
+
+ @Override
public void enableVideoCalling(boolean enable) {
final Phone defaultPhone = getDefaultPhone();
enforceModifyPermission();
@@ -7278,8 +7534,11 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public @Nullable PhoneAccountHandle getPhoneAccountHandleForSubscriptionId(int subscriptionId) {
- enforceReadPrivilegedPermission("getPhoneAccountHandleForSubscriptionId, "
- + "subscriptionId: " + subscriptionId);
+ TelephonyPermissions
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp,
+ subscriptionId,
+ "getPhoneAccountHandleForSubscriptionId, " + "subscriptionId: " + subscriptionId);
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -7350,7 +7609,11 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
return;
}
-
+ Phone defaultPhone = getDefaultPhone();
+ if (defaultPhone != null) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+ mApp, getDefaultPhone().getSubId(), "factoryReset");
+ }
final long identity = Binder.clearCallingIdentity();
try {
@@ -7362,7 +7625,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
Phone phone = getPhone(subId);
cleanUpAllowedNetworkTypes(phone, subId);
setDataRoamingEnabled(subId, getDefaultDataRoamingEnabled(subId));
- CarrierInfoManager.deleteAllCarrierKeysForImsiEncryption(mApp);
+ getPhone(subId).resetCarrierKeysForImsiEncryption();
}
// There has been issues when Sms raw table somehow stores orphan
// fragments. They lead to garbled message when new fragments come
@@ -7375,12 +7638,17 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
ImsManager.getInstance(mApp, slotId).factoryReset();
}
+ if (defaultPhone == null) {
+ return;
+ }
// Erase modem config if erase modem on network setting is enabled.
String configValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TELEPHONY,
RESET_NETWORK_ERASE_MODEM_CONFIG_ENABLED);
if (configValue != null && Boolean.parseBoolean(configValue)) {
- sendEraseModemConfig(getDefaultPhone());
+ sendEraseModemConfig(defaultPhone);
}
+
+ sendEraseDataInSharedPreferences(defaultPhone);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -7554,7 +7822,15 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
boolean hasCoarsePermission =
coarseLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+ final Phone phone = getPhone(subId);
+ if (phone == null) {
+ return null;
+ }
+
final long identity = Binder.clearCallingIdentity();
+
+ boolean isCallingPackageDataService = phone.getDataServicePackages()
+ .contains(callingPackage);
try {
// isActiveSubId requires READ_PHONE_STATE, which we already check for above
if (!mSubscriptionController.isActiveSubId(subId, callingPackage, callingFeatureId)) {
@@ -7563,16 +7839,11 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
return null;
}
- final Phone phone = getPhone(subId);
- if (phone == null) {
- return null;
- }
-
ServiceState ss = phone.getServiceState();
// Scrub out the location info in ServiceState depending on what level of access
// the caller has.
- if (hasFinePermission) return ss;
+ if (hasFinePermission || isCallingPackageDataService) return ss;
if (hasCoarsePermission) return ss.createLocationInfoSanitizedCopy(false);
return ss.createLocationInfoSanitizedCopy(true);
} finally {
@@ -9238,7 +9509,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
@Override
- public void setMobileDataPolicyEnabledStatus(int subscriptionId, int policy,
+ public void setMobileDataPolicyEnabled(int subscriptionId, int policy,
boolean enabled) {
enforceModifyPermission();
@@ -9468,14 +9739,13 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public boolean isRadioInterfaceCapabilitySupported(
- @NonNull @TelephonyManager.RadioInterfaceCapability String capability) {
+ final @NonNull @TelephonyManager.RadioInterfaceCapability String capability) {
Set<String> radioInterfaceCapabilities =
mRadioInterfaceCapabilities.getCapabilities();
if (radioInterfaceCapabilities == null) {
throw new RuntimeException("radio interface capabilities are not available");
- } else {
- return radioInterfaceCapabilities.contains(capability);
}
+ return radioInterfaceCapabilities.contains(capability);
}
@Override
@@ -9659,8 +9929,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
switch (thermalMitigationAction) {
case ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_DATA_THROTTLING:
thermalMitigationResult =
- handleDataThrottlingRequest(subId,
- thermalMitigationRequest.getDataThrottlingRequest());
+ handleDataThrottlingRequest(subId,
+ thermalMitigationRequest.getDataThrottlingRequest());
break;
case ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_VOICE_ONLY:
if (thermalMitigationRequest.getDataThrottlingRequest() != null) {
@@ -9691,7 +9961,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
Phone phone = getPhone(subId);
if (phone == null) {
thermalMitigationResult =
- TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
break;
}
@@ -9704,7 +9974,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
break;
} else if (isAnyPhoneInEmergencyState()) {
thermalMitigationResult =
- TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE;
+ TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE;
break;
}
} else {
@@ -9721,7 +9991,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
break;
}
thermalMitigationResult =
- TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
+ TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
break;
default:
throw new IllegalArgumentException("the requested thermalMitigationAction does "
@@ -9882,8 +10152,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
try {
if (!RcsProvisioningMonitor.getInstance()
.registerRcsProvisioningCallback(subId, callback)) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
- "Service not available for the subscription.");
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
+ "Active subscription not found.");
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -9964,11 +10234,15 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
IImsConfig configBinder = getImsConfig(getSlotIndex(subId), ImsFeature.FEATURE_RCS);
if (configBinder == null) {
Rlog.e(LOG_TAG, "null result for setRcsClientConfiguration");
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
+ "could not find the requested subscription");
} else {
configBinder.setRcsClientConfiguration(rcc);
}
} catch (RemoteException e) {
Rlog.e(LOG_TAG, "fail to setRcsClientConfiguration " + e.getMessage());
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+ "service is temporarily unavailable.");
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -10019,7 +10293,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
@Override
public void sendDeviceToDeviceMessage(int message, int value) {
TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
- "setCarrierSingleRegistrationEnabledOverride");
+ "sendDeviceToDeviceMessage");
enforceModifyPermission();
final long identity = Binder.clearCallingIdentity();
@@ -10036,6 +10310,55 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
}
+ /**
+ * Sets the specified device to device transport active.
+ * @param transport The transport to set active.
+ */
+ @Override
+ public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setActiveDeviceToDeviceTransport");
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ TelephonyConnectionService service =
+ TelecomAccountRegistry.getInstance(null).getTelephonyConnectionService();
+ if (service == null) {
+ Rlog.e(LOG_TAG, "setActiveDeviceToDeviceTransport: not in a call.");
+ return;
+ }
+ service.setActiveDeviceToDeviceTransport(transport);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setDeviceToDeviceForceEnabled(boolean isForceEnabled) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setDeviceToDeviceForceEnabled");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Arrays.stream(PhoneFactory.getPhones()).forEach(
+ p -> {
+ Phone thePhone = p.getImsPhone();
+ if (thePhone != null && thePhone instanceof ImsPhone) {
+ ImsPhone imsPhone = (ImsPhone) thePhone;
+ CallTracker tracker = imsPhone.getCallTracker();
+ if (tracker != null && tracker instanceof ImsPhoneCallTracker) {
+ ImsPhoneCallTracker imsPhoneCallTracker =
+ (ImsPhoneCallTracker) tracker;
+ imsPhoneCallTracker.setDeviceToDeviceForceEnabled(isForceEnabled);
+ }
+ }
+ }
+ );
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
/**
* Gets the config of RCS VoLTE single registration enabled for the device.
@@ -10125,6 +10448,21 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
/**
+ * Get the EAB capability from the EAB database.
+ */
+ @Override
+ public String getCapabilityFromEab(String contact) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getCapabilityFromEab");
+ enforceModifyPermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return EabUtil.getCapabilityFromEab(getDefaultPhone().getContext(), contact);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Remove the EAB contacts from the EAB database.
*/
@Override
@@ -10381,36 +10719,36 @@ public class PhoneInterfaceManager extends ITelephony.Stub {
}
/**
- * Prepare TelephonyManager for an unattended reboot. The reboot is
- * required to be done shortly after the API is invoked.
+ * Gets the current phone capability.
+ *
+ * Requires carrier privileges or READ_PRECISE_PHONE_STATE permission.
+ * @return the PhoneCapability which describes the data connection capability of modem.
+ * It's used to evaluate possible phone config change, for example from single
+ * SIM device to multi-SIM device.
*/
@Override
- @TelephonyManager.PrepareUnattendedRebootResult
- public int prepareForUnattendedReboot() {
- enforceRebootPermission();
-
+ public PhoneCapability getPhoneCapability() {
+ enforceReadPrivilegedPermission("getPhoneCapability");
final long identity = Binder.clearCallingIdentity();
try {
- return (int) sendRequest(CMD_PREPARE_UNATTENDED_REBOOT, null);
+ return mPhoneConfigurationManager.getCurrentPhoneCapability();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Gets the current phone capability.
- *
- * Requires carrier privileges or READ_PRECISE_PHONE_STATE permission.
- * @return the PhoneCapability which describes the data connection capability of modem.
- * It's used to evaluate possible phone config change, for example from single
- * SIM device to multi-SIM device.
+ * Prepare TelephonyManager for an unattended reboot. The reboot is
+ * required to be done shortly after the API is invoked.
*/
@Override
- public PhoneCapability getPhoneCapability() {
- enforceReadPrivilegedPermission("getPhoneCapability");
+ @TelephonyManager.PrepareUnattendedRebootResult
+ public int prepareForUnattendedReboot() {
+ enforceRebootPermission();
+
final long identity = Binder.clearCallingIdentity();
try {
- return mPhoneConfigurationManager.getCurrentPhoneCapability();
+ return (int) sendRequest(CMD_PREPARE_UNATTENDED_REBOOT, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 8f4cd86af..23c4c5a95 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -129,7 +129,6 @@ public class RcsProvisioningMonitor {
public void onRoleHoldersChanged(String role, UserHandle user) {
if (RoleManager.ROLE_SMS.equals(role)) {
logv("default messaging application changed.");
- String packageName = getDmaPackageName();
mHandler.sendEmptyMessage(EVENT_DMA_CHANGED);
}
}
@@ -676,6 +675,8 @@ public class RcsProvisioningMonitor {
logv("new default messaging application " + mDmaPackageName);
mRcsProvisioningInfos.forEach((k, v) -> {
+ notifyDmaForSub(k, v.getSingleRegistrationCapability());
+
byte[] cachedConfig = v.getConfig();
//clear old callbacks
v.clear();
@@ -803,7 +804,7 @@ public class RcsProvisioningMonitor {
intent.setPackage(mDmaPackageName);
intent.putExtra(ProvisioningManager.EXTRA_SUBSCRIPTION_ID, subId);
intent.putExtra(ProvisioningManager.EXTRA_STATUS, capability);
- logv("notify " + intent);
+ logv("notify " + intent + ", sub:" + subId + ", capability:" + capability);
// Only send permission to the default sms app if it has the correct permissions
// except test mode enabled
if (!mTestModeEnabled) {
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 2af08234a..dbeb7ce1d 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -21,16 +21,21 @@ import static com.android.internal.telephony.d2d.Communicator.MESSAGE_CALL_RADIO
import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_BATTERY_STATE;
import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE;
+import android.Manifest;
import android.content.Context;
+import android.net.Uri;
import android.os.Binder;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.provider.BlockedNumberContract;
+import android.telephony.BarringInfo;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsException;
import android.telephony.ims.RcsContactUceCapability;
@@ -39,6 +44,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
import com.android.ims.rcs.uce.util.FeatureTags;
import com.android.internal.telephony.ITelephony;
@@ -48,6 +54,7 @@ import com.android.internal.telephony.d2d.Communicator;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.phone.callcomposer.CallComposerPictureManager;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -58,6 +65,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
/**
* Takes actions based on the adb commands given by "adb shell cmd phone ...". Be careful, no
@@ -72,6 +81,7 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
private static final boolean VDBG = true;
private static final int DEFAULT_PHONE_ID = 0;
+ private static final String CALL_COMPOSER_SUBCOMMAND = "callcomposer";
private static final String IMS_SUBCOMMAND = "ims";
private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify";
private static final String EMERGENCY_CALLBACK_MODE = "emergency-callback-mode";
@@ -81,14 +91,17 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
private static final String UNATTENDED_REBOOT = "unattended-reboot";
private static final String CARRIER_CONFIG_SUBCOMMAND = "cc";
private static final String DATA_TEST_MODE = "data";
- private static final String DATA_ENABLE = "enable";
- private static final String DATA_DISABLE = "disable";
+ private static final String ENABLE = "enable";
+ private static final String DISABLE = "disable";
+ private static final String QUERY = "query";
+
+ private static final String CALL_COMPOSER_TEST_MODE = "test-mode";
+ private static final String CALL_COMPOSER_SIMULATE_CALL = "simulate-outgoing-call";
+ private static final String CALL_COMPOSER_USER_SETTING = "user-setting";
private static final String IMS_SET_IMS_SERVICE = "set-ims-service";
private static final String IMS_GET_IMS_SERVICE = "get-ims-service";
private static final String IMS_CLEAR_SERVICE_OVERRIDE = "clear-ims-service-override";
- private static final String IMS_ENABLE = "enable";
- private static final String IMS_DISABLE = "disable";
// Used to disable or enable processing of conference event package data from the network.
// This is handy for testing scenarios where CEP data does not exist on a network which does
// support CEP data.
@@ -119,9 +132,15 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
private static final String D2D_SUBCOMMAND = "d2d";
private static final String D2D_SEND = "send";
+ private static final String D2D_TRANSPORT = "transport";
+ private static final String D2D_SET_DEVICE_SUPPORT = "set-device-support";
+
+ private static final String BARRING_SUBCOMMAND = "barring";
+ private static final String BARRING_SEND_INFO = "send";
private static final String RCS_UCE_COMMAND = "uce";
private static final String UCE_GET_EAB_CONTACT = "get-eab-contact";
+ private static final String UCE_GET_EAB_CAPABILITY = "get-eab-capability";
private static final String UCE_REMOVE_EAB_CONTACT = "remove-eab-contact";
private static final String UCE_GET_DEVICE_ENABLED = "get-device-enabled";
private static final String UCE_SET_DEVICE_ENABLED = "set-device-enabled";
@@ -135,15 +154,23 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
// Check if a package has carrier privileges on any SIM, regardless of subId/phoneId.
private static final String HAS_CARRIER_PRIVILEGES_COMMAND = "has-carrier-privileges";
+ private static final String DISABLE_PHYSICAL_SUBSCRIPTION = "disable-physical-subscription";
+ private static final String ENABLE_PHYSICAL_SUBSCRIPTION = "enable-physical-subscription";
+
private static final String THERMAL_MITIGATION_COMMAND = "thermal-mitigation";
private static final String ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "allow-package";
private static final String DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "disallow-package";
+ private static final String GET_ALLOWED_NETWORK_TYPES_FOR_USER =
+ "get-allowed-network-types-for-users";
+ private static final String SET_ALLOWED_NETWORK_TYPES_FOR_USER =
+ "set-allowed-network-types-for-users";
// Take advantage of existing methods that already contain permissions checks when possible.
private final ITelephony mInterface;
private SubscriptionManager mSubscriptionManager;
private CarrierConfigManager mCarrierConfigManager;
+ private TelephonyRegistryManager mTelephonyRegistryManager;
private Context mContext;
private enum CcType {
@@ -237,6 +264,8 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
(CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
mSubscriptionManager = (SubscriptionManager)
context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mTelephonyRegistryManager = (TelephonyRegistryManager)
+ context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
mContext = context;
}
@@ -269,16 +298,27 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
return handleGbaCommand();
case D2D_SUBCOMMAND:
return handleD2dCommand();
+ case BARRING_SUBCOMMAND:
+ return handleBarringCommand();
case SINGLE_REGISTATION_CONFIG:
return handleSingleRegistrationConfigCommand();
case RESTART_MODEM:
return handleRestartModemCommand();
+ case CALL_COMPOSER_SUBCOMMAND:
+ return handleCallComposerCommand();
case UNATTENDED_REBOOT:
return handleUnattendedReboot();
case HAS_CARRIER_PRIVILEGES_COMMAND:
return handleHasCarrierPrivilegesCommand();
case THERMAL_MITIGATION_COMMAND:
return handleThermalMitigationCommand();
+ case DISABLE_PHYSICAL_SUBSCRIPTION:
+ return handleEnablePhysicalSubscription(false);
+ case ENABLE_PHYSICAL_SUBSCRIPTION:
+ return handleEnablePhysicalSubscription(true);
+ case GET_ALLOWED_NETWORK_TYPES_FOR_USER:
+ case SET_ALLOWED_NETWORK_TYPES_FOR_USER:
+ return handleAllowedNetworkTypesCommand(cmd);
default: {
return handleDefaultCommands(cmd);
}
@@ -313,6 +353,10 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
pw.println(" Prepare for unattended reboot.");
pw.println(" has-carrier-privileges [package]");
pw.println(" Query carrier privilege status for a package. Prints true or false.");
+ pw.println(" get-allowed-network-types-for-users");
+ pw.println(" Get the Allowed Network Types.");
+ pw.println(" set-allowed-network-types-for-users");
+ pw.println(" Set the Allowed Network Types.");
onHelpIms();
onHelpUce();
onHelpEmergencyNumber();
@@ -322,6 +366,8 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
onHelpGba();
onHelpSrc();
onHelpD2D();
+ onHelpDisableOrEnablePhysicalSubscription();
+ onHelpAllowedNetworkTypes();
}
private void onHelpD2D() {
@@ -338,6 +384,25 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
MESSAGE_DEVICE_BATTERY_STATE));
pw.println(" Type: " + MESSAGE_DEVICE_NETWORK_COVERAGE + " - "
+ Communicator.messageToString(MESSAGE_DEVICE_NETWORK_COVERAGE));
+ pw.println(" d2d transport TYPE");
+ pw.println(" Forces the specified D2D transport TYPE to be active. Use the");
+ pw.println(" short class name of the transport; i.e. DtmfTransport or RtpTransport.");
+ pw.println(" d2d set-device-support true/default");
+ pw.println(" true - forces device support to be enabled for D2D.");
+ pw.println(" default - clear any previously set force-enable of D2D, reverting to ");
+ pw.println(" the current device's configuration.");
+ }
+
+ private void onHelpBarring() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Barring Commands:");
+ pw.println(" barring send -s SLOT_ID -b BARRING_TYPE -c IS_CONDITIONALLY_BARRED"
+ + " -t CONDITIONAL_BARRING_TIME_SECS");
+ pw.println(" Notifies of a barring info change for the specified slot id.");
+ pw.println(" BARRING_TYPE: 0 for BARRING_TYPE_NONE");
+ pw.println(" BARRING_TYPE: 1 for BARRING_TYPE_UNCONDITIONAL");
+ pw.println(" BARRING_TYPE: 2 for BARRING_TYPE_CONDITIONAL");
+ pw.println(" BARRING_TYPE: -1 for BARRING_TYPE_UNKNOWN");
}
private void onHelpIms() {
@@ -439,6 +504,15 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
+ "mitigation.");
}
+ private void onHelpDisableOrEnablePhysicalSubscription() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Disable or enable a physical subscription");
+ pw.println(" disable-physical-subscription SUB_ID");
+ pw.println(" Disable the physical subscription with the provided subId, if allowed.");
+ pw.println(" enable-physical-subscription SUB_ID");
+ pw.println(" Enable the physical subscription with the provided subId, if allowed.");
+ }
+
private void onHelpDataTestMode() {
PrintWriter pw = getOutPrintWriter();
pw.println("Mobile Data Test Mode Commands:");
@@ -558,6 +632,30 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
pw.println(" is specified, it will choose the default voice SIM slot.");
}
+ private void onHelpAllowedNetworkTypes() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Allowed Network Types Commands:");
+ pw.println(" get-allowed-network-types-for-users [-s SLOT_ID]");
+ pw.println(" Print allowed network types value.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read allowed network types value for. If no");
+ pw.println(" option is specified, it will choose the default voice SIM slot.");
+ pw.println(" set-allowed-network-types-for-users [-s SLOT_ID] [NETWORK_TYPES_BITMASK]");
+ pw.println(" Sets allowed network types to NETWORK_TYPES_BITMASK.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to set allowed network types value for. If no");
+ pw.println(" option is specified, it will choose the default voice SIM slot.");
+ pw.println(" NETWORK_TYPES_BITMASK specifies the new network types value and this type");
+ pw.println(" is bitmask in binary format. Reference the NetworkTypeBitMask");
+ pw.println(" at TelephonyManager.java");
+ pw.println(" For example:");
+ pw.println(" NR only : 10000000000000000000");
+ pw.println(" NR|LTE : 11000001000000000000");
+ pw.println(" NR|LTE|CDMA|EVDO|GSM|WCDMA : 11001111101111111111");
+ pw.println(" LTE|CDMA|EVDO|GSM|WCDMA : 01001111101111111111");
+ pw.println(" LTE only : 01000001000000000000");
+ }
+
private int handleImsCommand() {
String arg = getNextArg();
if (arg == null) {
@@ -575,10 +673,10 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
case IMS_CLEAR_SERVICE_OVERRIDE: {
return handleImsClearCarrierServiceCommand();
}
- case IMS_ENABLE: {
+ case ENABLE: {
return handleEnableIms();
}
- case IMS_DISABLE: {
+ case DISABLE: {
return handleDisableIms();
}
case IMS_CEP: {
@@ -597,7 +695,7 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
return 0;
}
switch (arg) {
- case DATA_ENABLE: {
+ case ENABLE: {
try {
mInterface.enableDataConnectivity();
} catch (RemoteException ex) {
@@ -607,7 +705,7 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
}
break;
}
- case DATA_DISABLE: {
+ case DISABLE: {
try {
mInterface.disableDataConnectivity();
} catch (RemoteException ex) {
@@ -749,6 +847,41 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
return -1;
}
+ private boolean subIsEsim(int subId) {
+ SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfo(subId);
+ if (info != null) {
+ return info.isEmbedded();
+ }
+ return false;
+ }
+
+ private int handleEnablePhysicalSubscription(boolean enable) {
+ PrintWriter errPw = getErrPrintWriter();
+ int subId = 0;
+ try {
+ subId = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ errPw.println((enable ? "enable" : "disable")
+ + "-physical-subscription requires an integer as a subId.");
+ return -1;
+ }
+ // Verify that the user is allowed to run the command. Only allowed in rooted device in a
+ // non user build.
+ if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) {
+ errPw.println("cc: Permission denied.");
+ return -1;
+ }
+ // Verify that the subId represents a physical sub
+ if (subIsEsim(subId)) {
+ errPw.println("SubId " + subId + " is not for a physical subscription");
+ return -1;
+ }
+ Log.d(LOG_TAG, (enable ? "Enabling" : "Disabling")
+ + " physical subscription with subId=" + subId);
+ mSubscriptionManager.setUiccApplicationsEnabled(subId, enable);
+ return 0;
+ }
+
private int handleThermalMitigationCommand() {
String arg = getNextArg();
String packageName = getNextArg();
@@ -790,6 +923,12 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
case D2D_SEND: {
return handleD2dSendCommand();
}
+ case D2D_TRANSPORT: {
+ return handleD2dTransportCommand();
+ }
+ case D2D_SET_DEVICE_SUPPORT: {
+ return handleD2dDeviceSupportedCommand();
+ }
}
return -1;
@@ -797,11 +936,9 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
private int handleD2dSendCommand() {
PrintWriter errPw = getErrPrintWriter();
- String opt;
int messageType = -1;
int messageValue = -1;
-
String arg = getNextArg();
if (arg == null) {
onHelpD2D();
@@ -837,6 +974,132 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int handleD2dTransportCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+
+ try {
+ mInterface.setActiveDeviceToDeviceTransport(arg);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "d2d transport error: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+ private int handleBarringCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpBarring();
+ return 0;
+ }
+
+ switch (arg) {
+ case BARRING_SEND_INFO: {
+ return handleBarringSendCommand();
+ }
+ }
+ return -1;
+ }
+
+ private int handleBarringSendCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+ int slotId = getDefaultSlot();
+ int subId = SubscriptionManager.getSubId(slotId)[0];
+ @BarringInfo.BarringServiceInfo.BarringType int barringType =
+ BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL;
+ boolean isConditionallyBarred = false;
+ int conditionalBarringTimeSeconds = 0;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-s": {
+ try {
+ slotId = Integer.parseInt(getNextArgRequired());
+ subId = SubscriptionManager.getSubId(slotId)[0];
+ } catch (NumberFormatException e) {
+ errPw.println("barring send requires an integer as a SLOT_ID.");
+ return -1;
+ }
+ break;
+ }
+ case "-b": {
+ try {
+ barringType = Integer.parseInt(getNextArgRequired());
+ if (barringType < -1 || barringType > 2) {
+ throw new NumberFormatException();
+ }
+
+ } catch (NumberFormatException e) {
+ errPw.println("barring send requires an integer in range [-1,2] as "
+ + "a BARRING_TYPE.");
+ return -1;
+ }
+ break;
+ }
+ case "-c": {
+ try {
+ isConditionallyBarred = Boolean.parseBoolean(getNextArgRequired());
+ } catch (Exception e) {
+ errPw.println("barring send requires a boolean after -c indicating"
+ + " conditional barring");
+ return -1;
+ }
+ break;
+ }
+ case "-t": {
+ try {
+ conditionalBarringTimeSeconds = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ errPw.println("barring send requires an integer for time of barring"
+ + " in seconds after -t for conditional barring");
+ return -1;
+ }
+ break;
+ }
+ }
+ }
+ SparseArray<BarringInfo.BarringServiceInfo> barringServiceInfos = new SparseArray<>();
+ BarringInfo.BarringServiceInfo bsi = new BarringInfo.BarringServiceInfo(
+ barringType, isConditionallyBarred, 0, conditionalBarringTimeSeconds);
+ barringServiceInfos.append(0, bsi);
+ BarringInfo barringInfo = new BarringInfo(null, barringServiceInfos);
+ try {
+ mTelephonyRegistryManager.notifyBarringInfoChanged(slotId, subId, barringInfo);
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "barring send error: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleD2dDeviceSupportedCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+
+ boolean isEnabled = "true".equals(arg.toLowerCase());
+ try {
+ mInterface.setDeviceToDeviceForceEnabled(isEnabled);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Error forcing D2D enabled: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
// ims set-ims-service
private int handleImsSetServiceCommand() {
PrintWriter errPw = getErrPrintWriter();
@@ -1825,6 +2088,8 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
return handleRemovingEabContactCommand();
case UCE_GET_EAB_CONTACT:
return handleGettingEabContactCommand();
+ case UCE_GET_EAB_CAPABILITY:
+ return handleGettingEabCapabilityCommand();
case UCE_GET_DEVICE_ENABLED:
return handleUceGetDeviceEnabledCommand();
case UCE_SET_DEVICE_ENABLED:
@@ -1874,7 +2139,6 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
String result = "";
try {
result = mInterface.getContactFromEab(phoneNumber);
-
} catch (RemoteException e) {
Log.w(LOG_TAG, "uce get-eab-contact, error " + e.getMessage());
getErrPrintWriter().println("Exception: " + e.getMessage());
@@ -1888,6 +2152,27 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int handleGettingEabCapabilityCommand() {
+ String phoneNumber = getNextArgRequired();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ return -1;
+ }
+ String result = "";
+ try {
+ result = mInterface.getCapabilityFromEab(phoneNumber);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce get-eab-capability, error " + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce get-eab-capability, result: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
private int handleUceGetDeviceEnabledCommand() {
boolean result = false;
try {
@@ -2225,10 +2510,95 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
return 0;
}
+
+ private void onHelpCallComposer() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Call composer commands");
+ pw.println(" callcomposer test-mode enable|disable|query");
+ pw.println(" Enables or disables test mode for call composer. In test mode, picture");
+ pw.println(" upload/download from carrier servers is disabled, and operations are");
+ pw.println(" performed using emulated local files instead.");
+ pw.println(" callcomposer simulate-outgoing-call [subId] [UUID]");
+ pw.println(" Simulates an outgoing call being placed with the picture ID as");
+ pw.println(" the provided UUID. This triggers storage to the call log.");
+ pw.println(" callcomposer user-setting [subId] enable|disable|query");
+ pw.println(" Enables or disables the user setting for call composer, as set by");
+ pw.println(" TelephonyManager#setCallComposerStatus.");
+ }
+
+ private int handleCallComposerCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpCallComposer();
+ return 0;
+ }
+
+ mContext.enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE,
+ "MODIFY_PHONE_STATE required for call composer shell cmds");
+ switch (arg) {
+ case CALL_COMPOSER_TEST_MODE: {
+ String enabledStr = getNextArg();
+ if (ENABLE.equals(enabledStr)) {
+ CallComposerPictureManager.sTestMode = true;
+ } else if (DISABLE.equals(enabledStr)) {
+ CallComposerPictureManager.sTestMode = false;
+ } else if (QUERY.equals(enabledStr)) {
+ getOutPrintWriter().println(CallComposerPictureManager.sTestMode);
+ } else {
+ onHelpCallComposer();
+ return 1;
+ }
+ break;
+ }
+ case CALL_COMPOSER_SIMULATE_CALL: {
+ int subscriptionId = Integer.valueOf(getNextArg());
+ String uuidString = getNextArg();
+ UUID uuid = UUID.fromString(uuidString);
+ CompletableFuture<Uri> storageUriFuture = new CompletableFuture<>();
+ Binder.withCleanCallingIdentity(() -> {
+ CallComposerPictureManager.getInstance(mContext, subscriptionId)
+ .storeUploadedPictureToCallLog(uuid, storageUriFuture::complete);
+ });
+ try {
+ Uri uri = storageUriFuture.get();
+ getOutPrintWriter().println(String.valueOf(uri));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ break;
+ }
+ case CALL_COMPOSER_USER_SETTING: {
+ try {
+ int subscriptionId = Integer.valueOf(getNextArg());
+ String enabledStr = getNextArg();
+ if (ENABLE.equals(enabledStr)) {
+ mInterface.setCallComposerStatus(subscriptionId,
+ TelephonyManager.CALL_COMPOSER_STATUS_ON);
+ } else if (DISABLE.equals(enabledStr)) {
+ mInterface.setCallComposerStatus(subscriptionId,
+ TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+ } else if (QUERY.equals(enabledStr)) {
+ getOutPrintWriter().println(mInterface.getCallComposerStatus(subscriptionId)
+ == TelephonyManager.CALL_COMPOSER_STATUS_ON);
+ } else {
+ onHelpCallComposer();
+ return 1;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace(getOutPrintWriter());
+ return 1;
+ }
+ break;
+ }
+ }
+ return 0;
+ }
+
private int handleHasCarrierPrivilegesCommand() {
String packageName = getNextArgRequired();
boolean hasCarrierPrivileges;
+ final long token = Binder.clearCallingIdentity();
try {
hasCarrierPrivileges =
mInterface.checkCarrierPrivilegesForPackageAnyPhone(packageName)
@@ -2237,9 +2607,122 @@ public class TelephonyShellCommand extends BasicShellCommandHandler {
Log.w(LOG_TAG, HAS_CARRIER_PRIVILEGES_COMMAND + " exception", e);
getErrPrintWriter().println("Exception: " + e.getMessage());
return -1;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
getOutPrintWriter().println(hasCarrierPrivileges);
return 0;
}
+
+ private int handleAllowedNetworkTypesCommand(String command) {
+ if (!checkShellUid()) {
+ return -1;
+ }
+
+ PrintWriter errPw = getErrPrintWriter();
+ String tag = command + ": ";
+ String opt;
+ int subId = -1;
+ Log.v(LOG_TAG, command + " start");
+
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("-s")) {
+ try {
+ subId = slotStringToSubId(tag, getNextArgRequired());
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ errPw.println(tag + "No valid subscription found.");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ // Missing slot id
+ errPw.println(tag + "SLOT_ID expected after -s.");
+ return -1;
+ }
+ } else {
+ errPw.println(tag + "Unknown option " + opt);
+ return -1;
+ }
+ }
+
+ if (GET_ALLOWED_NETWORK_TYPES_FOR_USER.equals(command)) {
+ return handleGetAllowedNetworkTypesCommand(subId);
+ }
+ if (SET_ALLOWED_NETWORK_TYPES_FOR_USER.equals(command)) {
+ return handleSetAllowedNetworkTypesCommand(subId);
+ }
+ return -1;
+ }
+
+ private int handleGetAllowedNetworkTypesCommand(int subId) {
+ PrintWriter errPw = getErrPrintWriter();
+
+ long result = -1;
+ try {
+ if (mInterface != null) {
+ result = mInterface.getAllowedNetworkTypesForReason(subId,
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getAllowedNetworkTypesForReason RemoteException" + e);
+ errPw.println(GET_ALLOWED_NETWORK_TYPES_FOR_USER + "RemoteException " + e);
+ return -1;
+ }
+
+ getOutPrintWriter().println(TelephonyManager.convertNetworkTypeBitmaskToString(result));
+ return 0;
+ }
+
+ private int handleSetAllowedNetworkTypesCommand(int subId) {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String bitmaskString = getNextArg();
+ if (TextUtils.isEmpty(bitmaskString)) {
+ errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " No NETWORK_TYPES_BITMASK");
+ return -1;
+ }
+ long allowedNetworkTypes = convertNetworkTypeBitmaskFromStringToLong(bitmaskString);
+ if (allowedNetworkTypes < 0) {
+ errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " No valid NETWORK_TYPES_BITMASK");
+ return -1;
+ }
+ boolean result = false;
+ try {
+ if (mInterface != null) {
+ result = mInterface.setAllowedNetworkTypesForReason(subId,
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER, allowedNetworkTypes);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "setAllowedNetworkTypesForReason RemoteException" + e);
+ errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " RemoteException " + e);
+ return -1;
+ }
+
+ String resultMessage = SET_ALLOWED_NETWORK_TYPES_FOR_USER + " failed";
+ if (result) {
+ resultMessage = SET_ALLOWED_NETWORK_TYPES_FOR_USER + " completed";
+ }
+ getOutPrintWriter().println(resultMessage);
+ return 0;
+ }
+
+ private long convertNetworkTypeBitmaskFromStringToLong(String bitmaskString) {
+ if (TextUtils.isEmpty(bitmaskString)) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "AllowedNetworkTypes:" + bitmaskString
+ + ", length: " + bitmaskString.length());
+ }
+ try {
+ return Long.parseLong(bitmaskString, 2);
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "AllowedNetworkTypes: " + e);
+ return -1;
+ }
+ }
}
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureManager.java b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
new file mode 100644
index 000000000..efb149e73
--- /dev/null
+++ b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+public class CallComposerPictureManager {
+ private static final String TAG = CallComposerPictureManager.class.getSimpleName();
+ private static final SparseArray<CallComposerPictureManager> sInstances = new SparseArray<>();
+ private static final String THREE_GPP_BOOTSTRAPPING = "3GPP-bootstrapping";
+
+ public static CallComposerPictureManager getInstance(Context context, int subscriptionId) {
+ synchronized (sInstances) {
+ if (sExecutorService == null) {
+ sExecutorService = Executors.newSingleThreadScheduledExecutor();
+ }
+ if (!sInstances.contains(subscriptionId)) {
+ sInstances.put(subscriptionId,
+ new CallComposerPictureManager(context, subscriptionId));
+ }
+ return sInstances.get(subscriptionId);
+ }
+ }
+
+ @VisibleForTesting
+ public static void clearInstances() {
+ synchronized (sInstances) {
+ sInstances.clear();
+ if (sExecutorService != null) {
+ sExecutorService.shutdown();
+ sExecutorService = null;
+ }
+ }
+ }
+
+ // disabled provisionally until the auth stack is fully operational
+ @VisibleForTesting
+ public static boolean sTestMode = false;
+ public static final String FAKE_SERVER_URL = "https://example.com/FAKE.png";
+ public static final String FAKE_SUBJECT = "This is a test call subject";
+ public static final Location FAKE_LOCATION = new Location("");
+ static {
+ // Meteor Crater, AZ
+ FAKE_LOCATION.setLatitude(35.027526);
+ FAKE_LOCATION.setLongitude(-111.021696);
+ }
+
+ public interface CallLogProxy {
+ default void storeCallComposerPictureAsUser(Context context,
+ UserHandle user,
+ InputStream input,
+ Executor executor,
+ OutcomeReceiver<Uri, CallLog.CallComposerLoggingException> callback) {
+ CallLog.storeCallComposerPicture(context.createContextAsUser(user, 0),
+ input, executor, callback);
+ }
+ }
+
+ private static ScheduledExecutorService sExecutorService = null;
+
+ private final HashMap<UUID, String> mCachedServerUrls = new HashMap<>();
+ private final HashMap<UUID, ImageData> mCachedImages = new HashMap<>();
+ private GbaCredentials mCachedCredentials = null;
+ private final int mSubscriptionId;
+ private final TelephonyManager mTelephonyManager;
+ private final Context mContext;
+ private CallLogProxy mCallLogProxy = new CallLogProxy() {};
+
+ private CallComposerPictureManager(Context context, int subscriptionId) {
+ mContext = context;
+ mSubscriptionId = subscriptionId;
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(mSubscriptionId);
+ }
+
+ public void handleUploadToServer(CallComposerPictureTransfer.Factory transferFactory,
+ ImageData imageData, Consumer<Pair<UUID, Integer>> callback) {
+ if (sTestMode) {
+ UUID id = UUID.randomUUID();
+ mCachedImages.put(id, imageData);
+ mCachedServerUrls.put(id, FAKE_SERVER_URL);
+ callback.accept(Pair.create(id, TelephonyManager.CallComposerException.SUCCESS));
+ return;
+ }
+
+ PersistableBundle carrierConfig = mTelephonyManager.getCarrierConfig();
+ String uploadUrl = carrierConfig.getString(
+ CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
+ if (TextUtils.isEmpty(uploadUrl)) {
+ Log.e(TAG, "Call composer upload URL not configured in carrier config");
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_UNKNOWN));
+ }
+ UUID id = UUID.randomUUID();
+ imageData.setId(id.toString());
+
+ CallComposerPictureTransfer transfer = transferFactory.create(mContext,
+ mSubscriptionId, uploadUrl, sExecutorService);
+
+ AtomicBoolean hasRetried = new AtomicBoolean(false);
+ transfer.setCallback(new CallComposerPictureTransfer.PictureCallback() {
+ @Override
+ public void onError(int error) {
+ callback.accept(Pair.create(null, error));
+ }
+
+ @Override
+ public void onRetryNeeded(boolean credentialRefresh, long backoffMillis) {
+ if (hasRetried.getAndSet(true)) {
+ Log.e(TAG, "Giving up on image upload after one retry.");
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_NETWORK_UNAVAILABLE));
+ return;
+ }
+ GbaCredentialsSupplier supplier =
+ (realm, executor) ->
+ getGbaCredentials(credentialRefresh, carrierConfig, executor);
+
+ sExecutorService.schedule(() -> transfer.uploadPicture(imageData, supplier),
+ backoffMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onUploadSuccessful(String serverUrl) {
+ mCachedServerUrls.put(id, serverUrl);
+ mCachedImages.put(id, imageData);
+ Log.i(TAG, "Successfully received url: " + serverUrl + " associated with "
+ + id.toString());
+ callback.accept(Pair.create(id, TelephonyManager.CallComposerException.SUCCESS));
+ }
+ });
+
+ transfer.uploadPicture(imageData,
+ (realm, executor) -> getGbaCredentials(false, carrierConfig, executor));
+ }
+
+ public void handleDownloadFromServer(CallComposerPictureTransfer.Factory transferFactory,
+ String remoteUrl, Consumer<Pair<Uri, Integer>> callback) {
+ if (sTestMode) {
+ ImageData imageData = new ImageData(getPlaceholderPictureAsBytes(), "image/png", null);
+ UUID id = UUID.randomUUID();
+ mCachedImages.put(id, imageData);
+ storeUploadedPictureToCallLog(id, uri -> callback.accept(Pair.create(uri, -1)));
+ return;
+ }
+
+ PersistableBundle carrierConfig = mTelephonyManager.getCarrierConfig();
+ CallComposerPictureTransfer transfer = transferFactory.create(mContext,
+ mSubscriptionId, remoteUrl, sExecutorService);
+
+ AtomicBoolean hasRetried = new AtomicBoolean(false);
+ transfer.setCallback(new CallComposerPictureTransfer.PictureCallback() {
+ @Override
+ public void onError(int error) {
+ callback.accept(Pair.create(null, error));
+ }
+
+ @Override
+ public void onRetryNeeded(boolean credentialRefresh, long backoffMillis) {
+ if (hasRetried.getAndSet(true)) {
+ Log.e(TAG, "Giving up on image download after one retry.");
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_NETWORK_UNAVAILABLE));
+ return;
+ }
+ GbaCredentialsSupplier supplier =
+ (realm, executor) ->
+ getGbaCredentials(credentialRefresh, carrierConfig, executor);
+
+ sExecutorService.schedule(() -> transfer.downloadPicture(supplier),
+ backoffMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onDownloadSuccessful(ImageData data) {
+ ByteArrayInputStream imageDataInput =
+ new ByteArrayInputStream(data.getImageBytes());
+ mCallLogProxy.storeCallComposerPictureAsUser(
+ mContext, UserHandle.CURRENT, imageDataInput,
+ sExecutorService,
+ new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+ @Override
+ public void onResult(@NonNull Uri result) {
+ callback.accept(Pair.create(
+ result, TelephonyManager.CallComposerException.SUCCESS));
+ }
+
+ @Override
+ public void onError(CallLog.CallComposerLoggingException e) {
+ // Just report an error to the client for now.
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_UNKNOWN));
+ }
+ });
+ }
+ });
+
+ transfer.downloadPicture(((realm, executor) ->
+ getGbaCredentials(false, carrierConfig, executor)));
+ }
+
+ public void storeUploadedPictureToCallLog(UUID id, Consumer<Uri> callback) {
+ ImageData data = mCachedImages.get(id);
+ if (data == null) {
+ Log.e(TAG, "No picture associated with uuid " + id);
+ callback.accept(null);
+ return;
+ }
+ ByteArrayInputStream imageDataInput =
+ new ByteArrayInputStream(data.getImageBytes());
+ mCallLogProxy.storeCallComposerPictureAsUser(mContext, UserHandle.CURRENT, imageDataInput,
+ sExecutorService,
+ new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+ @Override
+ public void onResult(@NonNull Uri result) {
+ callback.accept(result);
+ clearCachedData();
+ }
+
+ @Override
+ public void onError(CallLog.CallComposerLoggingException e) {
+ // Just report an error to the client for now.
+ Log.e(TAG, "Error logging uploaded image: " + e.getErrorCode());
+ callback.accept(null);
+ clearCachedData();
+ }
+ });
+ }
+
+ public String getServerUrlForImageId(UUID id) {
+ return mCachedServerUrls.get(id);
+ }
+
+ public void clearCachedData() {
+ mCachedServerUrls.clear();
+ mCachedImages.clear();
+ }
+
+ private byte[] getPlaceholderPictureAsBytes() {
+ InputStream resourceInput = mContext.getResources().openRawResource(R.drawable.cupcake);
+ try {
+ return readBytes(resourceInput);
+ } catch (Exception e) {
+ return new byte[] {};
+ }
+ }
+
+ private static byte[] readBytes(InputStream inputStream) throws Exception {
+ byte[] buffer = new byte[1024];
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int numRead;
+ do {
+ numRead = inputStream.read(buffer);
+ if (numRead > 0) output.write(buffer, 0, numRead);
+ } while (numRead > 0);
+ return output.toByteArray();
+ }
+
+ private CompletableFuture<GbaCredentials> getGbaCredentials(
+ boolean forceRefresh, PersistableBundle config, Executor executor) {
+ synchronized (this) {
+ if (!forceRefresh && mCachedCredentials != null) {
+ return CompletableFuture.completedFuture(mCachedCredentials);
+ }
+
+ if (forceRefresh) {
+ mCachedCredentials = null;
+ }
+ }
+
+ UaSecurityProtocolIdentifier securityProtocolIdentifier =
+ new UaSecurityProtocolIdentifier.Builder()
+ .setOrg(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_ORGANIZATION_INT))
+ .setProtocol(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_PROTOCOL_INT))
+ .setTlsCipherSuite(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_TLS_CIPHER_SUITE_INT))
+ .build();
+ CompletableFuture<GbaCredentials> resultFuture = new CompletableFuture<>();
+
+ mTelephonyManager.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_ISIM,
+ getNafUri(config), securityProtocolIdentifier, forceRefresh, executor,
+ new TelephonyManager.BootstrapAuthenticationCallback() {
+ @Override
+ public void onKeysAvailable(byte[] gbaKey, String transactionId) {
+ GbaCredentials creds = new GbaCredentials(transactionId, gbaKey);
+ synchronized (CallComposerPictureManager.this) {
+ mCachedCredentials = creds;
+ }
+ resultFuture.complete(creds);
+ }
+
+ @Override
+ public void onAuthenticationFailure(int reason) {
+ Log.e(TAG, "GBA auth failed: reason=" + reason);
+ resultFuture.complete(null);
+ }
+ });
+
+ return resultFuture;
+ }
+
+ private static Uri getNafUri(PersistableBundle carrierConfig) {
+ String uploadUriString = carrierConfig.getString(
+ CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
+ Uri uploadUri = Uri.parse(uploadUriString);
+ String nafPrefix;
+ switch (carrierConfig.getInt(CarrierConfigManager.KEY_GBA_MODE_INT)) {
+ case CarrierConfigManager.GBA_U:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING + "-uicc";
+ break;
+ case CarrierConfigManager.GBA_DIGEST:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING + "-digest";
+ break;
+ case CarrierConfigManager.GBA_ME:
+ default:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING;
+ }
+ String newAuthority = nafPrefix + "@" + uploadUri.getAuthority();
+ Uri nafUri = new Uri.Builder().scheme(uploadUri.getScheme())
+ .encodedAuthority(newAuthority)
+ .build();
+ Log.i(TAG, "using NAF uri " + nafUri + " for GBA");
+ return nafUri;
+ }
+
+ @VisibleForTesting
+ static ScheduledExecutorService getExecutor() {
+ return sExecutorService;
+ }
+
+ @VisibleForTesting
+ void setCallLogProxy(CallLogProxy proxy) {
+ mCallLogProxy = proxy;
+ }
+}
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java b/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java
new file mode 100644
index 000000000..e4458cd8c
--- /dev/null
+++ b/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.http.multipart.MultipartEntity;
+import com.android.internal.http.multipart.Part;
+
+import com.google.common.net.MediaType;
+
+import gov.nist.javax.sip.header.WWWAuthenticate;
+
+import org.xml.sax.InputSource;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Iterator;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+public class CallComposerPictureTransfer {
+ private static final String TAG = CallComposerPictureTransfer.class.getSimpleName();
+ private static final int HTTP_TIMEOUT_MILLIS = 20000;
+ private static final int DEFAULT_BACKOFF_MILLIS = 1000;
+ private static final String THREE_GPP_GBA = "3gpp-gba";
+
+ private static final int ERROR_UNKNOWN = 0;
+ private static final int ERROR_HTTP_TIMEOUT = 1;
+ private static final int ERROR_NO_AUTH_REQUIRED = 2;
+ private static final int ERROR_FORBIDDEN = 3;
+
+ public interface Factory {
+ default CallComposerPictureTransfer create(Context context, int subscriptionId, String url,
+ ExecutorService executorService) {
+ return new CallComposerPictureTransfer(context, subscriptionId, url, executorService);
+ }
+ }
+
+ public interface PictureCallback {
+ default void onError(@TelephonyManager.CallComposerException.CallComposerError int error) {}
+ default void onRetryNeeded(boolean credentialRefresh, long backoffMillis) {}
+ default void onUploadSuccessful(String serverUrl) {}
+ default void onDownloadSuccessful(ImageData data) {}
+ }
+
+ private static class NetworkAccessException extends RuntimeException {
+ final int errorCode;
+
+ NetworkAccessException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+ }
+
+ private final Context mContext;
+ private final int mSubscriptionId;
+ private final String mUrl;
+ private final ExecutorService mExecutorService;
+
+ private PictureCallback mCallback;
+
+ private CallComposerPictureTransfer(Context context, int subscriptionId, String url,
+ ExecutorService executorService) {
+ mContext = context;
+ mSubscriptionId = subscriptionId;
+ mExecutorService = executorService;
+ mUrl = url;
+ }
+
+ @VisibleForTesting
+ public void setCallback(PictureCallback callback) {
+ mCallback = callback;
+ }
+
+ public void uploadPicture(ImageData image,
+ GbaCredentialsSupplier credentialsSupplier) {
+ CompletableFuture<Network> networkFuture = getNetworkForCallComposer();
+ CompletableFuture<WWWAuthenticate> authorizationHeaderFuture = networkFuture
+ .thenApplyAsync((network) -> prepareInitialPost(network, mUrl), mExecutorService)
+ .thenComposeAsync(this::obtainAuthenticateHeader, mExecutorService)
+ .thenApplyAsync(DigestAuthUtils::parseAuthenticateHeader);
+ CompletableFuture<GbaCredentials> credsFuture = authorizationHeaderFuture
+ .thenComposeAsync((header) ->
+ credentialsSupplier.getCredentials(header.getRealm(), mExecutorService),
+ mExecutorService);
+
+ CompletableFuture<String> authorizationFuture =
+ authorizationHeaderFuture.thenCombineAsync(credsFuture,
+ (authHeader, credentials) ->
+ DigestAuthUtils.generateAuthorizationHeader(
+ authHeader, credentials, "POST", mUrl),
+ mExecutorService)
+ .whenCompleteAsync(
+ (authorization, error) -> handleExceptionalCompletion(error),
+ mExecutorService);
+
+ CompletableFuture<String> networkUrlFuture =
+ networkFuture.thenCombineAsync(authorizationFuture,
+ (network, auth) -> sendActualImageUpload(network, auth, image),
+ mExecutorService);
+ networkUrlFuture.thenAcceptAsync((result) -> {
+ if (result != null) mCallback.onUploadSuccessful(result);
+ }, mExecutorService).exceptionally((ex) -> {
+ logException("Exception uploading image" , ex);
+ return null;
+ });
+ }
+
+ public void downloadPicture(GbaCredentialsSupplier credentialsSupplier) {
+ CompletableFuture<Network> networkFuture = getNetworkForCallComposer();
+ CompletableFuture<HttpURLConnection> getConnectionFuture =
+ networkFuture.thenApplyAsync((network) ->
+ prepareImageDownloadRequest(network, mUrl), mExecutorService);
+
+ CompletableFuture<ImageData> immediatelyDownloadableImage = getConnectionFuture
+ .thenComposeAsync((conn) -> {
+ try {
+ if (conn.getResponseCode() != 200) {
+ return CompletableFuture.completedFuture(null);
+ }
+ } catch (IOException e) {
+ logException("IOException obtaining return code: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+ return CompletableFuture.completedFuture(downloadImageFromConnection(conn));
+ }, mExecutorService);
+
+ CompletableFuture<ImageData> authRequiredImage = getConnectionFuture
+ .thenComposeAsync((conn) -> {
+ try {
+ if (conn.getResponseCode() == 200) {
+ // handled by above case
+ return CompletableFuture.completedFuture(null);
+ }
+ } catch (IOException e) {
+ logException("IOException obtaining return code: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+ CompletableFuture<WWWAuthenticate> authenticateHeaderFuture =
+ obtainAuthenticateHeader(conn)
+ .thenApply(DigestAuthUtils::parseAuthenticateHeader);
+ CompletableFuture<GbaCredentials> credsFuture = authenticateHeaderFuture
+ .thenComposeAsync((header) ->
+ credentialsSupplier.getCredentials(header.getRealm(),
+ mExecutorService), mExecutorService);
+
+ CompletableFuture<String> authorizationFuture = authenticateHeaderFuture
+ .thenCombineAsync(credsFuture, (authHeader, credentials) ->
+ DigestAuthUtils.generateAuthorizationHeader(
+ authHeader, credentials, "GET", mUrl),
+ mExecutorService)
+ .whenCompleteAsync((authorization, error) ->
+ handleExceptionalCompletion(error), mExecutorService);
+
+ return networkFuture.thenCombineAsync(authorizationFuture,
+ this::downloadImageWithAuth, mExecutorService);
+ }, mExecutorService);
+
+ CompletableFuture.allOf(immediatelyDownloadableImage, authRequiredImage).thenRun(() -> {
+ ImageData fromImmediate = immediatelyDownloadableImage.getNow(null);
+ ImageData fromAuth = authRequiredImage.getNow(null);
+ // If both of these are null, that means an error happened somewhere in the chain.
+ // in that case, the error has already been transmitted to the callback, so ignore it.
+ if (fromAuth == null && fromImmediate == null) {
+ Log.w(TAG, "No result from download -- error happened sometime earlier");
+ }
+ if (fromAuth != null) mCallback.onDownloadSuccessful(fromAuth);
+ mCallback.onDownloadSuccessful(fromImmediate);
+ }).exceptionally((ex) -> {
+ logException("Exception downloading image" , ex);
+ return null;
+ });
+ }
+
+ private CompletableFuture<Network> getNetworkForCallComposer() {
+ ConnectivityManager connectivityManager =
+ mContext.getSystemService(ConnectivityManager.class);
+ NetworkRequest pictureNetworkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ CompletableFuture<Network> resultFuture = new CompletableFuture<>();
+ connectivityManager.requestNetwork(pictureNetworkRequest,
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ resultFuture.complete(network);
+ }
+ });
+ return resultFuture;
+ }
+
+ private HttpURLConnection prepareInitialPost(Network network, String uploadUrl) {
+ try {
+ HttpURLConnection connection =
+ (HttpURLConnection) network.openConnection(new URL(uploadUrl));
+ connection.setRequestMethod("POST");
+ connection.setInstanceFollowRedirects(false);
+ connection.setConnectTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setReadTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setRequestProperty("User-Agent", getUserAgent());
+ return connection;
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Malformed URL: " + uploadUrl);
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ logException("IOException opening network: ", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private HttpURLConnection prepareImageDownloadRequest(Network network, String imageUrl) {
+ try {
+ HttpURLConnection connection =
+ (HttpURLConnection) network.openConnection(new URL(imageUrl));
+ connection.setRequestMethod("GET");
+ connection.setConnectTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setReadTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setRequestProperty("User-Agent", getUserAgent());
+ return connection;
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Malformed URL: " + imageUrl);
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ logException("IOException opening network: ", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Attempts to connect via the supplied connection, expecting a HTTP 401 in response. Throws
+ // an IOException if the connection times out.
+ // After the response is received, returns the WWW-Authenticate header in the following form:
+ // "WWW-Authenticate:<method> <params>"
+ private CompletableFuture<String> obtainAuthenticateHeader(
+ HttpURLConnection connection) {
+ return CompletableFuture.supplyAsync(() -> {
+ int responseCode;
+ try {
+ responseCode = connection.getResponseCode();
+ } catch (IOException e) {
+ logException("IOException obtaining auth header: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+ if (responseCode == 204) {
+ throw new NetworkAccessException(ERROR_NO_AUTH_REQUIRED);
+ } else if (responseCode == 403) {
+ throw new NetworkAccessException(ERROR_FORBIDDEN);
+ } else if (responseCode != 401) {
+ Log.w(TAG, "Received unexpected response in auth request, code= "
+ + responseCode);
+ throw new NetworkAccessException(ERROR_UNKNOWN);
+ }
+
+ return connection.getHeaderField(DigestAuthUtils.WWW_AUTHENTICATE);
+ }, mExecutorService);
+ }
+
+ private ImageData downloadImageWithAuth(Network network, String authorization) {
+ HttpURLConnection connection = prepareImageDownloadRequest(network, mUrl);
+ connection.addRequestProperty("Authorization", authorization);
+ return downloadImageFromConnection(connection);
+ }
+
+ private ImageData downloadImageFromConnection(HttpURLConnection conn) {
+ try {
+ if (conn.getResponseCode() != 200) {
+ Log.w(TAG, "Got response code " + conn.getResponseCode() + " when trying"
+ + " to download image");
+ if (conn.getResponseCode() == 401) {
+ Log.i(TAG, "Got 401 even with auth -- key refresh needed?");
+ mCallback.onRetryNeeded(true, 0);
+ }
+ return null;
+ }
+ } catch (IOException e) {
+ logException("IOException obtaining return code: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+
+ String contentType = conn.getContentType();
+ ByteArrayOutputStream imageDataOut = new ByteArrayOutputStream();
+ byte[] buffer = new byte[4096];
+ int numRead;
+ try {
+ InputStream is = conn.getInputStream();
+ while (true) {
+ numRead = is.read(buffer);
+ if (numRead < 0) break;
+ imageDataOut.write(buffer, 0, numRead);
+ }
+ } catch (IOException e) {
+ logException("IOException reading from image body: ", e);
+ return null;
+ }
+
+ return new ImageData(imageDataOut.toByteArray(), contentType, null);
+ }
+
+ private void handleExceptionalCompletion(Throwable error) {
+ if (error != null) {
+ if (error.getCause() instanceof NetworkAccessException) {
+ int code = ((NetworkAccessException) error.getCause()).errorCode;
+ if (code == ERROR_UNKNOWN || code == ERROR_HTTP_TIMEOUT) {
+ scheduleRetry();
+ } else {
+ int failureCode;
+ if (code == ERROR_FORBIDDEN) {
+ failureCode = TelephonyManager.CallComposerException
+ .ERROR_AUTHENTICATION_FAILED;
+ } else {
+ failureCode = TelephonyManager.CallComposerException
+ .ERROR_UNKNOWN;
+ }
+ deliverFailure(failureCode);
+ }
+ } else {
+ deliverFailure(TelephonyManager.CallComposerException.ERROR_UNKNOWN);
+ }
+ }
+ }
+
+ private void scheduleRetry() {
+ mCallback.onRetryNeeded(false, DEFAULT_BACKOFF_MILLIS);
+ }
+
+ private void deliverFailure(int code) {
+ mCallback.onError(code);
+ }
+
+ private static Part makeUploadPart(String name, String contentType, String filename,
+ byte[] data) {
+ return new Part() {
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public String getCharSet() {
+ return null;
+ }
+
+ @Override
+ public String getTransferEncoding() {
+ return null;
+ }
+
+ @Override
+ public void sendDispositionHeader(OutputStream out) throws IOException {
+ super.sendDispositionHeader(out);
+ if (filename != null) {
+ String fileNameSuffix = "; filename=\"" + filename + "\"";
+ out.write(fileNameSuffix.getBytes());
+ }
+ }
+
+ @Override
+ protected void sendData(OutputStream out) throws IOException {
+ out.write(data);
+ }
+
+ @Override
+ protected long lengthOfData() throws IOException {
+ return data.length;
+ }
+ };
+ }
+
+ private String sendActualImageUpload(Network network, String authHeader, ImageData image) {
+ Part transactionIdPart = makeUploadPart("tid", "text/plain",
+ null, image.getId().getBytes());
+ Part imageDataPart = makeUploadPart("File", image.getMimeType(),
+ image.getId(), image.getImageBytes());
+
+ MultipartEntity multipartEntity =
+ new MultipartEntity(new Part[] {transactionIdPart, imageDataPart});
+
+ HttpURLConnection connection = prepareInitialPost(network, mUrl);
+ connection.setDoOutput(true);
+ connection.addRequestProperty("Authorization", authHeader);
+ connection.addRequestProperty("Content-Length",
+ String.valueOf(multipartEntity.getContentLength()));
+ connection.addRequestProperty("Content-Type", multipartEntity.getContentType().getValue());
+ connection.addRequestProperty("Accept-Encoding", "*");
+
+ try (OutputStream requestBodyOut = connection.getOutputStream()) {
+ multipartEntity.writeTo(requestBodyOut);
+ } catch (IOException e) {
+ logException("IOException making request to upload image: ", e);
+ throw new RuntimeException(e);
+ }
+
+ try {
+ int response = connection.getResponseCode();
+ Log.i(TAG, "Received response code: " + response
+ + ", message=" + connection.getResponseMessage());
+ if (response == 401 || response == 403) {
+ deliverFailure(TelephonyManager.CallComposerException.ERROR_AUTHENTICATION_FAILED);
+ return null;
+ }
+ if (response == 503) {
+ // TODO: implement parsing of retry-after and schedule a retry with that time
+ scheduleRetry();
+ return null;
+ }
+ if (response != 200) {
+ scheduleRetry();
+ return null;
+ }
+ String responseBody = readResponseBody(connection);
+ String parsedUrl = parseImageUploadResponseXmlForUrl(responseBody);
+ Log.i(TAG, "Parsed URL as upload result: " + parsedUrl);
+ return parsedUrl;
+ } catch (IOException e) {
+ logException("IOException getting response to image upload: ", e);
+ deliverFailure(TelephonyManager.CallComposerException.ERROR_UNKNOWN);
+ return null;
+ }
+ }
+
+ private static String parseImageUploadResponseXmlForUrl(String xmlData) {
+ NamespaceContext ns = new NamespaceContext() {
+ public String getNamespaceURI(String prefix) {
+ return "urn:gsma:params:xml:ns:rcs:rcs:fthttp";
+ }
+
+ public String getPrefix(String uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Iterator getPrefixes(String uri) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ xPath.setNamespaceContext(ns);
+ StringReader reader = new StringReader(xmlData);
+ try {
+ return (String) xPath.evaluate("/a:file/a:file-info[@type='file']/a:data/@url",
+ new InputSource(reader), XPathConstants.STRING);
+ } catch (XPathExpressionException e) {
+ logException("Error parsing response XML:", e);
+ return null;
+ }
+ }
+
+ private static String readResponseBody(HttpURLConnection connection) {
+ Charset charset = MediaType.parse(connection.getContentType())
+ .charset().or(Charset.defaultCharset());
+ StringBuilder sb = new StringBuilder();
+ try (InputStream inputStream = connection.getInputStream()) {
+ String outLine;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset));
+ while ((outLine = reader.readLine()) != null) {
+ sb.append(outLine);
+ }
+ } catch (IOException e) {
+ logException("IOException reading request body: ", e);
+ return null;
+ }
+ return sb.toString();
+ }
+
+ private String getUserAgent() {
+ String carrierName = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(mSubscriptionId)
+ .getSimOperatorName();
+ String buildId = Build.ID;
+ String buildDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.ofEpochMilli(Build.TIME));
+ String buildVersion = Build.VERSION.RELEASE_OR_CODENAME;
+ String deviceName = Build.DEVICE;
+ return String.format("%s %s %s %s %s %s %s",
+ carrierName, buildId, buildDate, "Android", buildVersion,
+ deviceName, THREE_GPP_GBA);
+
+ }
+
+ private static void logException(String message, Throwable e) {
+ StringWriter log = new StringWriter();
+ log.append(message);
+ log.append(":\n");
+ log.append(e.getMessage());
+ PrintWriter pw = new PrintWriter(log);
+ e.printStackTrace(pw);
+ Log.e(TAG, log.toString());
+ }
+}
diff --git a/src/com/android/phone/callcomposer/DigestAuthUtils.java b/src/com/android/phone/callcomposer/DigestAuthUtils.java
new file mode 100644
index 000000000..2f081f712
--- /dev/null
+++ b/src/com/android/phone/callcomposer/DigestAuthUtils.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 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.phone.callcomposer;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.common.io.BaseEncoding;
+
+import gov.nist.javax.sip.address.GenericURI;
+import gov.nist.javax.sip.header.Authorization;
+import gov.nist.javax.sip.header.WWWAuthenticate;
+import gov.nist.javax.sip.parser.WWWAuthenticateParser;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.text.ParseException;
+
+public class DigestAuthUtils {
+ private static final String TAG = DigestAuthUtils.class.getSimpleName();
+
+ public static final String WWW_AUTHENTICATE = "www-authenticate";
+ private static final String MD5_ALGORITHM = "md5";
+ private static final int CNONCE_LENGTH_BYTES = 16;
+ private static final String AUTH_QOP = "auth";
+
+ public static WWWAuthenticate parseAuthenticateHeader(String header) {
+ String reconstitutedHeader = WWW_AUTHENTICATE + ": " + header;
+ WWWAuthenticate parsedHeader;
+ try {
+ return (WWWAuthenticate) (new WWWAuthenticateParser(reconstitutedHeader).parse());
+ } catch (ParseException e) {
+ Log.e(TAG, "Error parsing received auth header: " + e);
+ return null;
+ }
+ }
+
+ // Generates the Authorization header for use in future requests to the call composer server.
+ public static String generateAuthorizationHeader(WWWAuthenticate parsedHeader,
+ GbaCredentials credentials, String method, String uri) {
+ if (!TextUtils.isEmpty(parsedHeader.getAlgorithm())
+ && !MD5_ALGORITHM.equals(parsedHeader.getAlgorithm().toLowerCase())) {
+ Log.e(TAG, "This client only supports MD5 auth");
+ return "";
+ }
+ if (!TextUtils.isEmpty(parsedHeader.getQop())
+ && !AUTH_QOP.equals(parsedHeader.getQop().toLowerCase())) {
+ Log.e(TAG, "This client only supports the auth qop");
+ return "";
+ }
+
+ String clientNonce = makeClientNonce();
+
+ String response = computeResponse(parsedHeader.getNonce(), clientNonce, AUTH_QOP,
+ credentials.getTransactionId(), parsedHeader.getRealm(), credentials.getKey(),
+ method, uri);
+
+ Authorization replyHeader = new Authorization();
+ try {
+ replyHeader.setScheme(parsedHeader.getScheme());
+ replyHeader.setUsername(credentials.getTransactionId());
+ replyHeader.setURI(new WorkaroundURI(uri));
+ replyHeader.setRealm(parsedHeader.getRealm());
+ replyHeader.setQop(AUTH_QOP);
+ replyHeader.setNonce(parsedHeader.getNonce());
+ replyHeader.setCNonce(clientNonce);
+ replyHeader.setNonceCount(1);
+ replyHeader.setResponse(response);
+ replyHeader.setOpaque(parsedHeader.getOpaque());
+ replyHeader.setAlgorithm(parsedHeader.getAlgorithm());
+
+ } catch (ParseException e) {
+ Log.e(TAG, "Error parsing while constructing reply header: " + e);
+ return null;
+ }
+
+ return replyHeader.encodeBody();
+ }
+
+ public static String computeResponse(String serverNonce, String clientNonce, String qop,
+ String username, String realm, byte[] password, String method, String uri) {
+ String a1Hash = generateA1Hash(username, realm, password);
+ String a2Hash = generateA2Hash(method, uri);
+
+ // this is the nonce-count; since we don't reuse, it's always 1
+ String nonceCount = "00000001";
+ MessageDigest md5Digest = getMd5Digest();
+
+ String hashInput = String.join(":",
+ a1Hash,
+ serverNonce,
+ nonceCount,
+ clientNonce,
+ qop,
+ a2Hash);
+ md5Digest.update(hashInput.getBytes());
+ return base16(md5Digest.digest());
+ }
+
+ private static String makeClientNonce() {
+ SecureRandom rand = new SecureRandom();
+ byte[] clientNonceBytes = new byte[CNONCE_LENGTH_BYTES];
+ rand.nextBytes(clientNonceBytes);
+ return base16(clientNonceBytes);
+ }
+
+ private static String generateA1Hash(
+ String bootstrapTransactionId, String realm, byte[] gbaKey) {
+ MessageDigest md5Digest = getMd5Digest();
+
+ String gbaKeyBase64 = BaseEncoding.base64().encode(gbaKey);
+ String hashInput = String.join(":", bootstrapTransactionId, realm, gbaKeyBase64);
+ md5Digest.update(hashInput.getBytes());
+
+ return base16(md5Digest.digest());
+ }
+
+ private static String generateA2Hash(String method, String requestUri) {
+ MessageDigest md5Digest = getMd5Digest();
+ md5Digest.update(String.join(":", method, requestUri).getBytes());
+ return base16(md5Digest.digest());
+ }
+
+ private static String base16(byte[] input) {
+ return BaseEncoding.base16().encode(input).toLowerCase();
+ }
+
+ private static MessageDigest getMd5Digest() {
+ try {
+ return MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Couldn't find MD5 algorithm: " + e);
+ }
+ }
+
+ private static class WorkaroundURI extends GenericURI {
+ public WorkaroundURI(String uriString) {
+ this.uriString = uriString;
+ this.scheme = "";
+ }
+ }
+}
diff --git a/src/com/android/phone/callcomposer/GbaCredentials.java b/src/com/android/phone/callcomposer/GbaCredentials.java
new file mode 100644
index 000000000..25a0cd505
--- /dev/null
+++ b/src/com/android/phone/callcomposer/GbaCredentials.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+public class GbaCredentials {
+ private final String mTransactionId;
+ private final byte[] mKey;
+
+ public GbaCredentials(String transactionId, byte[] key) {
+ mTransactionId = transactionId;
+ mKey = key;
+ }
+
+ public String getTransactionId() {
+ return mTransactionId;
+ }
+
+ public byte[] getKey() {
+ return mKey;
+ }
+}
diff --git a/src/com/android/phone/callcomposer/GbaCredentialsSupplier.java b/src/com/android/phone/callcomposer/GbaCredentialsSupplier.java
new file mode 100644
index 000000000..9e5bb6aee
--- /dev/null
+++ b/src/com/android/phone/callcomposer/GbaCredentialsSupplier.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 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.phone.callcomposer;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+public interface GbaCredentialsSupplier {
+ CompletableFuture<GbaCredentials> getCredentials(String realm, Executor executor);
+}
diff --git a/src/com/android/phone/callcomposer/ImageData.java b/src/com/android/phone/callcomposer/ImageData.java
new file mode 100644
index 000000000..fc934855d
--- /dev/null
+++ b/src/com/android/phone/callcomposer/ImageData.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+public class ImageData {
+ private final byte[] mImageBytes;
+ private final String mMimeType;
+
+ private String mId;
+
+ public ImageData(byte[] imageBytes, String mimeType, String id) {
+ mImageBytes = imageBytes;
+ mMimeType = mimeType;
+ mId = id;
+ }
+
+ public byte[] getImageBytes() {
+ return mImageBytes;
+ }
+
+ public String getMimeType() {
+ return mMimeType;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public void setId(String id) {
+ mId = id;
+ }
+}
diff --git a/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
index 57caedee0..f24e7d6f5 100644
--- a/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
+++ b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
@@ -26,7 +26,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.UserHandle;
-import android.permission.PermissionManager;
+import android.permission.LegacyPermissionManager;
import android.service.euicc.EuiccService;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
@@ -55,14 +55,15 @@ public class EuiccUiDispatcherActivity extends Activity {
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
| PackageManager.GET_RESOLVED_FILTER;
- private PermissionManager mPermissionManager;
+ private LegacyPermissionManager mPermissionManager;
private boolean mGrantPermissionDone = false;
private ThreadPoolExecutor mExecutor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mPermissionManager = (PermissionManager) getSystemService(Context.PERMISSION_SERVICE);
+ mPermissionManager = (LegacyPermissionManager) getSystemService(
+ Context.LEGACY_PERMISSION_SERVICE);
mExecutor = new ThreadPoolExecutor(
1 /* corePoolSize */,
1 /* maxPoolSize */,
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index 8355fa6f8..475d878ed 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -64,7 +64,7 @@ public class AccessibilitySettingsFragment extends PreferenceFragment {
private final class AccessibilityTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.CallStateListener {
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
+ public void onCallStateChanged(int state) {
if (DBG) Log.d(LOG_TAG, "PhoneStateListener.onCallStateChanged: state=" + state);
Preference pref = getPreferenceScreen().findPreference(BUTTON_TTY_KEY);
if (pref != null) {
@@ -78,7 +78,7 @@ public class AccessibilitySettingsFragment extends PreferenceFragment {
|| (telephonyManager.getCallState() == TelephonyManager.CALL_STATE_IDLE));
}
}
- };
+ }
private Context mContext;
private AudioManager mAudioManager;
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 224a1f90d..6bc71dc48 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -78,6 +78,9 @@ public class PhoneAccountSettingsFragment extends PreferenceFragment
new SubscriptionManager.OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
+ if (getActivity() == null) {
+ return;
+ }
updateAccounts();
}
};
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 302fc4776..b1ab41376 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -17,6 +17,7 @@
package com.android.phone.settings;
import static android.net.ConnectivityManager.NetworkCallback;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
@@ -56,9 +57,9 @@ import android.telephony.CellSignalStrengthGsm;
import android.telephony.CellSignalStrengthLte;
import android.telephony.CellSignalStrengthWcdma;
import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.data.NetworkSlicingConfig;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhysicalChannelConfig;
-import android.telephony.PreciseCallState;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -96,8 +97,12 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
/**
@@ -211,6 +216,11 @@ public class RadioInfo extends AppCompatActivity {
private static final int MENU_ITEM_GET_IMS_STATUS = 4;
private static final int MENU_ITEM_TOGGLE_DATA = 5;
+ private static final String CARRIER_PROVISIONING_ACTION =
+ "com.android.phone.settings.CARRIER_PROVISIONING";
+ private static final String TRIGGER_CARRIER_PROVISIONING_ACTION =
+ "com.android.phone.settings.TRIGGER_CARRIER_PROVISIONING";
+
private TextView mDeviceId; //DeviceId is the IMEI in GSM and the MEID in CDMA
private TextView mLine1Number;
private TextView mSubscriptionId;
@@ -241,6 +251,7 @@ public class RadioInfo extends AppCompatActivity {
private TextView mNrAvailable;
private TextView mNrState;
private TextView mNrFrequency;
+ private TextView mNetworkSlicingConfig;
private EditText mSmsc;
private Switch mRadioPowerOnSwitch;
private Button mCellInfoRefreshRateButton;
@@ -295,6 +306,8 @@ public class RadioInfo extends AppCompatActivity {
}
};
+ private static final int DEFAULT_TIMEOUT_MS = 1000;
+
// not final because we need to recreate this object to register on a new subId (b/117555407)
private TelephonyCallback mTelephonyCallback = new RadioInfoTelephonyCallback();
private class RadioInfoTelephonyCallback extends TelephonyCallback implements
@@ -305,8 +318,7 @@ public class RadioInfo extends AppCompatActivity {
TelephonyCallback.CallForwardingIndicatorListener,
TelephonyCallback.CellInfoListener,
TelephonyCallback.SignalStrengthsListener,
- TelephonyCallback.ServiceStateListener,
- TelephonyCallback.PreciseCallStateListener {
+ TelephonyCallback.ServiceStateListener {
@Override
public void onDataConnectionStateChanged(int state, int networkType) {
@@ -320,17 +332,12 @@ public class RadioInfo extends AppCompatActivity {
}
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
+ public void onCallStateChanged(int state) {
updateNetworkType();
updatePhoneState(state);
}
@Override
- public void onPreciseCallStateChanged(PreciseCallState preciseState) {
- updateNetworkType();
- }
-
- @Override
public void onMessageWaitingIndicatorChanged(boolean mwi) {
mMwiValue = mwi;
updateMessageWaiting();
@@ -496,6 +503,7 @@ public class RadioInfo extends AppCompatActivity {
mNrState = (TextView) findViewById(R.id.nr_state);
mNrFrequency = (TextView) findViewById(R.id.nr_frequency);
mPhyChanConfig = (TextView) findViewById(R.id.phy_chan_config);
+ mNetworkSlicingConfig = (TextView) findViewById(R.id.network_slicing_config);
// hide 5G stats on devices that don't support 5G
if ((mTelephonyManager.getSupportedRadioAccessFamily()
@@ -510,6 +518,8 @@ public class RadioInfo extends AppCompatActivity {
mNrState.setVisibility(View.GONE);
((TextView) findViewById(R.id.nr_frequency_label)).setVisibility(View.GONE);
mNrFrequency.setVisibility(View.GONE);
+ ((TextView) findViewById(R.id.network_slicing_config_label)).setVisibility(View.GONE);
+ mNetworkSlicingConfig.setVisibility(View.GONE);
}
mPreferredNetworkType = (Spinner) findViewById(R.id.preferredNetworkType);
@@ -578,11 +588,20 @@ public class RadioInfo extends AppCompatActivity {
mDnsCheckToggleButton = (Button) findViewById(R.id.dns_check_toggle);
mDnsCheckToggleButton.setOnClickListener(mDnsCheckButtonHandler);
mCarrierProvisioningButton = (Button) findViewById(R.id.carrier_provisioning);
- mCarrierProvisioningButton.setOnClickListener(mCarrierProvisioningButtonHandler);
+ if (!TextUtils.isEmpty(getCarrierProvisioningAppString())) {
+ mCarrierProvisioningButton.setOnClickListener(mCarrierProvisioningButtonHandler);
+ } else {
+ mCarrierProvisioningButton.setEnabled(false);
+ }
+
mTriggerCarrierProvisioningButton = (Button) findViewById(
R.id.trigger_carrier_provisioning);
- mTriggerCarrierProvisioningButton.setOnClickListener(
- mTriggerCarrierProvisioningButtonHandler);
+ if (!TextUtils.isEmpty(getCarrierProvisioningAppString())) {
+ mTriggerCarrierProvisioningButton.setOnClickListener(
+ mTriggerCarrierProvisioningButtonHandler);
+ } else {
+ mTriggerCarrierProvisioningButton.setEnabled(false);
+ }
mOemInfoButton = (Button) findViewById(R.id.oem_info);
mOemInfoButton.setOnClickListener(mOemInfoButtonHandler);
@@ -647,6 +666,8 @@ public class RadioInfo extends AppCompatActivity {
mCellInfoRefreshRateSpinner.setOnItemSelectedListener(mCellInfoRefreshRateHandler);
//set selection after registering listener to force update
mCellInfoRefreshRateSpinner.setSelection(mCellInfoRefreshRateIndex);
+ // Request cell information update from RIL.
+ mTelephonyManager.setCellInfoListRate(CELL_INFO_REFRESH_RATES[mCellInfoRefreshRateIndex]);
//set selection before registering to prevent update
mPreferredNetworkType.setSelection(mPreferredNetworkTypeResult, true);
@@ -1116,6 +1137,19 @@ public class RadioInfo extends AppCompatActivity {
mNrState.setText(NetworkRegistrationInfo.nrStateToString(ss.getNrState()));
mNrFrequency.setText(ServiceState.frequencyRangeToString(ss.getNrFrequencyRange()));
}
+
+ Executor simpleExecutor = (r) -> r.run();
+ CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
+ mTelephonyManager.getNetworkSlicingConfiguration(simpleExecutor, resultFuture::complete);
+ try {
+ NetworkSlicingConfig networkSlicingConfig =
+ resultFuture.get(DEFAULT_TIMEOUT_MS, MILLISECONDS);
+ mNetworkSlicingConfig.setText(networkSlicingConfig.toString());
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ Log.e(TAG, "Unable to get slicing config: " + e.toString());
+ mNetworkSlicingConfig.setText("Unable to get slicing config.");
+ }
+
}
private void updateProperties() {
@@ -1615,21 +1649,23 @@ public class RadioInfo extends AppCompatActivity {
}
};
- OnClickListener mCarrierProvisioningButtonHandler = new OnClickListener() {
- public void onClick(View v) {
- final Intent intent = new Intent("com.android.settings.CARRIER_PROVISIONING");
- final ComponentName serviceComponent = ComponentName.unflattenFromString(
- "com.android.omadm.service/.DMIntentReceiver");
+ OnClickListener mCarrierProvisioningButtonHandler = v -> {
+ String carrierProvisioningApp = getCarrierProvisioningAppString();
+ if (!TextUtils.isEmpty(carrierProvisioningApp)) {
+ final Intent intent = new Intent(CARRIER_PROVISIONING_ACTION);
+ final ComponentName serviceComponent =
+ ComponentName.unflattenFromString(carrierProvisioningApp);
intent.setComponent(serviceComponent);
sendBroadcast(intent);
}
};
- OnClickListener mTriggerCarrierProvisioningButtonHandler = new OnClickListener() {
- public void onClick(View v) {
- final Intent intent = new Intent("com.android.settings.TRIGGER_CARRIER_PROVISIONING");
- final ComponentName serviceComponent = ComponentName.unflattenFromString(
- "com.android.omadm.service/.DMIntentReceiver");
+ OnClickListener mTriggerCarrierProvisioningButtonHandler = v -> {
+ String carrierProvisioningApp = getCarrierProvisioningAppString();
+ if (!TextUtils.isEmpty(carrierProvisioningApp)) {
+ final Intent intent = new Intent(TRIGGER_CARRIER_PROVISIONING_ACTION);
+ final ComponentName serviceComponent =
+ ComponentName.unflattenFromString(carrierProvisioningApp);
intent.setComponent(serviceComponent);
sendBroadcast(intent);
}
@@ -1694,6 +1730,19 @@ public class RadioInfo extends AppCompatActivity {
}
};
+ private String getCarrierProvisioningAppString() {
+ if (mPhone != null) {
+ CarrierConfigManager configManager =
+ mPhone.getContext().getSystemService(CarrierConfigManager.class);
+ PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+ if (b != null) {
+ return b.getString(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONING_APP_STRING, "");
+ }
+ }
+ return "";
+ }
+
boolean isCbrsSupported() {
return getResources().getBoolean(
com.android.internal.R.bool.config_cbrs_supported);
diff --git a/src/com/android/services/telephony/CdmaConferenceController.java b/src/com/android/services/telephony/CdmaConferenceController.java
index 8523a5ff4..a076ec840 100644
--- a/src/com/android/services/telephony/CdmaConferenceController.java
+++ b/src/com/android/services/telephony/CdmaConferenceController.java
@@ -20,11 +20,14 @@ import android.os.Handler;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
+import android.util.ArraySet;
import com.android.phone.PhoneUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* Manages CDMA conference calls. CDMA conference calls are much more limited than GSM conference
@@ -83,6 +86,9 @@ final class CdmaConferenceController {
private final Handler mHandler = new Handler();
+ private final Set<CdmaConnection> mPendingAddConnections = Collections.synchronizedSet(
+ new ArraySet<>());
+
public CdmaConferenceController(TelephonyConnectionService connectionService) {
mConnectionService = connectionService;
}
@@ -91,7 +97,7 @@ final class CdmaConferenceController {
private CdmaConference mConference;
void add(final CdmaConnection connection) {
- if (mCdmaConnections.contains(connection)) {
+ if (mCdmaConnections.contains(connection) || !mPendingAddConnections.add(connection)) {
// Adding a duplicate realistically shouldn't happen.
Log.w(this, "add - connection already tracked; connection=%s", connection);
return;
@@ -140,6 +146,7 @@ final class CdmaConferenceController {
private void addInternal(CdmaConnection connection) {
mCdmaConnections.add(connection);
+ mPendingAddConnections.remove(connection);
connection.addTelephonyConnectionListener(mTelephonyConnectionListener);
recalculateConference();
}
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index e17577fc6..9c89e9e1e 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -23,6 +23,7 @@ import android.provider.Settings;
import android.telecom.DisconnectCause;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsReasonInfo;
import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.Phone;
@@ -64,13 +65,13 @@ public class DisconnectCauseUtil {
* message and tone.
*
* @param telephonyDisconnectCause The code for the reason for the disconnect.
- * @param telephonyPerciseDisconnectCause The code for the percise reason for the disconnect.
+ * @param telephonyPreciseDisconnectCause The code for the precise reason for the disconnect.
* @param reason Description of the reason for the disconnect, not intended for the user to see..
*/
public static DisconnectCause toTelecomDisconnectCause(
- int telephonyDisconnectCause, int telephonyPerciseDisconnectCause, String reason) {
- return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPerciseDisconnectCause,
- reason, SubscriptionManager.getDefaultVoicePhoneId());
+ int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason) {
+ return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
+ reason, SubscriptionManager.getDefaultVoicePhoneId(), null);
}
/**
@@ -84,30 +85,33 @@ public class DisconnectCauseUtil {
public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
String reason, int phoneId) {
return toTelecomDisconnectCause(telephonyDisconnectCause, CallFailCause.NOT_VALID,
- reason, phoneId);
+ reason, phoneId, null);
}
/**
* Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
* generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
* message and tone for Slot.
- *
* @param telephonyDisconnectCause The code for the reason for the disconnect.
- * @param telephonyPerciseDisconnectCause The code for the percise reason for the disconnect.
- * @param reason Description of the reason for the disconnect, not intended for the user to see..
+ * @param telephonyPreciseDisconnectCause The code for the precise reason for the disconnect.
+ * @param reason Description of the reason for the disconnect, not intended for the user to see.
* @param phoneId To support localized message based on phoneId
+ * @param imsReasonInfo
*/
public static DisconnectCause toTelecomDisconnectCause(
- int telephonyDisconnectCause, int telephonyPerciseDisconnectCause, String reason,
- int phoneId) {
+ int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
+ int phoneId, ImsReasonInfo imsReasonInfo) {
Context context = PhoneGlobals.getInstance();
return new DisconnectCause(
toTelecomDisconnectCauseCode(telephonyDisconnectCause),
toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
- telephonyPerciseDisconnectCause),
+ telephonyPreciseDisconnectCause),
toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
toTelecomDisconnectReason(context,telephonyDisconnectCause, reason, phoneId),
- toTelecomDisconnectCauseTone(telephonyDisconnectCause, phoneId));
+ toTelecomDisconnectCauseTone(telephonyDisconnectCause, phoneId),
+ telephonyDisconnectCause,
+ telephonyPreciseDisconnectCause,
+ imsReasonInfo);
}
/**
@@ -233,10 +237,10 @@ public class DisconnectCauseUtil {
* Returns a label for to the disconnect cause to be shown to the user.
*/
private static CharSequence toTelecomDisconnectCauseLabel(
- Context context, int telephonyDisconnectCause, int telephonyPerciseDisconnectCause) {
+ Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause) {
CharSequence label;
- if (telephonyPerciseDisconnectCause != CallFailCause.NOT_VALID) {
- label = getLabelFromPreciseDisconnectCause(context, telephonyPerciseDisconnectCause,
+ if (telephonyPreciseDisconnectCause != CallFailCause.NOT_VALID) {
+ label = getLabelFromPreciseDisconnectCause(context, telephonyPreciseDisconnectCause,
telephonyDisconnectCause);
} else {
label = getLabelFromDisconnectCause(context, telephonyDisconnectCause);
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index c9f762be4..0be927a8c 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -1074,7 +1074,12 @@ public class ImsConference extends TelephonyConferenceBase implements Holdable {
// If the conference is empty and we're supposed to do a local disconnect, do so now.
if (mCarrierConfig.shouldLocalDisconnectEmptyConference()
- && oldParticipantCount > 0 && newParticipantCount == 0) {
+ // If we dropped from > 0 participants to zero
+ // OR if the conference had a single participant and is emulating a standalone
+ // call.
+ && (oldParticipantCount > 0 || !isMultiparty())
+ // AND the CEP says there is nobody left any more.
+ && newParticipantCount == 0) {
Log.i(this, "handleConferenceParticipantsUpdate: empty conference; "
+ "local disconnect.");
onDisconnect();
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index ee4baae64..41913678c 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -41,6 +41,7 @@ import com.android.internal.telephony.imsphone.ImsExternalConnection;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.NumberVerificationManager;
import com.android.phone.PhoneUtils;
+import com.android.phone.callcomposer.CallComposerPictureManager;
import com.android.telephony.Rlog;
import java.util.List;
@@ -297,6 +298,17 @@ final class PstnIncomingCallNotifier {
if (imsCall != null) {
ImsCallProfile imsCallProfile = imsCall.getCallProfile();
if (imsCallProfile != null) {
+ if (CallComposerPictureManager.sTestMode) {
+ imsCallProfile.setCallExtra(ImsCallProfile.EXTRA_PICTURE_URL,
+ CallComposerPictureManager.FAKE_SERVER_URL);
+ imsCallProfile.setCallExtraInt(ImsCallProfile.EXTRA_PRIORITY,
+ TelecomManager.PRIORITY_URGENT);
+ imsCallProfile.setCallExtra(ImsCallProfile.EXTRA_CALL_SUBJECT,
+ CallComposerPictureManager.FAKE_SUBJECT);
+ imsCallProfile.setCallExtraParcelable(ImsCallProfile.EXTRA_LOCATION,
+ CallComposerPictureManager.FAKE_LOCATION);
+ }
+
extras.putInt(TelecomManager.EXTRA_PRIORITY,
imsCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_PRIORITY));
extras.putString(TelecomManager.EXTRA_CALL_SUBJECT,
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 9226cae0c..1d749f4ac 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -588,11 +588,15 @@ public class TelecomAccountRegistry {
private boolean isCarrierVideoPresenceSupported() {
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- boolean carrierConfigEnabled = b != null
- && (b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL)
- || b.getBoolean(
- CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL));
- return carrierConfigEnabled && isUserContactDiscoverySettingEnabled();
+ if (b == null) return false;
+
+ // If using the new RcsUceAdapter API, this should be true if
+ // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is set. If using the old
+ // KEY_USE_RCS_PRESENCE_BOOL key, we have to also check the user setting.
+ return b.getBoolean(
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL)
+ || (b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL)
+ && isUserContactDiscoverySettingEnabled());
}
/**
@@ -1007,6 +1011,9 @@ public class TelecomAccountRegistry {
return mMmTelManager.isAvailable(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE)
|| mMmTelManager.isAvailable(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE)
+ || mMmTelManager.isAvailable(
+ ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
}
}
@@ -1111,7 +1118,7 @@ public class TelecomAccountRegistry {
}
}
}
- };
+ }
private static TelecomAccountRegistry sInstance;
private final Context mContext;
@@ -1203,7 +1210,7 @@ public class TelecomAccountRegistry {
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if merging calls is supported.
*/
- boolean isMergeCallSupported(PhoneAccountHandle handle) {
+ public boolean isMergeCallSupported(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -1221,7 +1228,7 @@ public class TelecomAccountRegistry {
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if video conferencing is supported.
*/
- boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
+ public boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -1239,7 +1246,7 @@ public class TelecomAccountRegistry {
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if merging of wifi calls is allowed when VoWIFI is disabled.
*/
- boolean isMergeOfWifiCallsAllowedWhenVoWifiOff(final PhoneAccountHandle handle) {
+ public boolean isMergeOfWifiCallsAllowedWhenVoWifiOff(final PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
Optional<AccountEntry> result = mAccounts.stream().filter(
entry -> entry.getPhoneAccountHandle().equals(handle)).findFirst();
@@ -1259,7 +1266,7 @@ public class TelecomAccountRegistry {
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if merging IMS calls is supported.
*/
- boolean isMergeImsCallSupported(PhoneAccountHandle handle) {
+ public boolean isMergeImsCallSupported(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 78942a159..18a40cf13 100755..100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -28,8 +28,11 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Messenger;
import android.os.PersistableBundle;
import android.telecom.CallAudioState;
+import android.telecom.CallDiagnostics;
+import android.telecom.CallScreeningService;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionService;
@@ -46,10 +49,12 @@ import android.telephony.ServiceState.RilRadioTechnology;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsStreamMediaProfile;
import android.telephony.ims.RtpHeaderExtension;
import android.telephony.ims.RtpHeaderExtensionType;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Pair;
import com.android.ims.ImsCall;
@@ -67,9 +72,11 @@ import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.d2d.Communicator;
import com.android.internal.telephony.d2d.DtmfAdapter;
import com.android.internal.telephony.d2d.DtmfTransport;
+import com.android.internal.telephony.d2d.MessageTypeAndValueHelper;
import com.android.internal.telephony.d2d.RtpAdapter;
import com.android.internal.telephony.d2d.RtpTransport;
import com.android.internal.telephony.d2d.Timeouts;
+import com.android.internal.telephony.d2d.TransportProtocol;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -79,6 +86,8 @@ import com.android.phone.ImsUtil;
import com.android.phone.PhoneGlobals;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
+import com.android.phone.callcomposer.CallComposerPictureManager;
+import com.android.phone.callcomposer.CallComposerPictureTransfer;
import com.android.telephony.Rlog;
import java.util.ArrayList;
@@ -91,6 +100,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
/**
* Base class for CDMA and GSM connections.
@@ -129,6 +139,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
private static final int MSG_ON_CONNECTION_EVENT = 19;
private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
private static final int MSG_REJECT = 21;
+ private static final int MSG_DTMF_DONE = 22;
+ private static final int MSG_MEDIA_ATTRIBUTES_CHANGED = 23;
private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
@@ -145,39 +157,15 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
updateState();
break;
case MSG_HANDOVER_STATE_CHANGED:
+ // fall through
case MSG_REDIAL_CONNECTION_CHANGED:
String what = (msg.what == MSG_HANDOVER_STATE_CHANGED)
? "MSG_HANDOVER_STATE_CHANGED" : "MSG_REDIAL_CONNECTION_CHANGED";
- Log.v(TelephonyConnection.this, what);
+ Log.i(TelephonyConnection.this, "Connection changed due to: %s", what);
AsyncResult ar = (AsyncResult) msg.obj;
com.android.internal.telephony.Connection connection =
(com.android.internal.telephony.Connection) ar.result;
- if (connection == null) {
- setDisconnected(DisconnectCauseUtil
- .toTelecomDisconnectCause(DisconnectCause.OUT_OF_NETWORK,
- "handover failure, no connection"));
- close();
- break;
- }
- if (mOriginalConnection != null) {
- if (connection != null &&
- ((connection.getAddress() != null &&
- mOriginalConnection.getAddress() != null &&
- mOriginalConnection.getAddress().equals(connection.getAddress())) ||
- connection.getState() == mOriginalConnection.getStateBeforeHandover())) {
- Log.d(TelephonyConnection.this, "Setting original connection after"
- + " handover or redial, current original connection="
- + mOriginalConnection.toString()
- + ", new original connection="
- + connection.toString());
- setOriginalConnection(connection);
- mWasImsConnection = false;
- }
- } else {
- Log.w(TelephonyConnection.this,
- what + ": mOriginalConnection==null --"
- + " invalid state (not cleaned up)");
- }
+ onOriginalConnectionRedialed(connection);
break;
case MSG_RINGBACK_TONE:
Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
@@ -244,6 +232,10 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
setAudioQuality(audioQuality);
break;
+ case MSG_MEDIA_ATTRIBUTES_CHANGED:
+ refreshCodec();
+ break;
+
case MSG_SET_CONFERENCE_PARTICIPANTS:
List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj;
updateConferenceParticipants(participants);
@@ -299,6 +291,9 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
int rejectReason = (int) msg.obj;
reject(rejectReason);
break;
+ case MSG_DTMF_DONE:
+ Log.i(this, "MSG_DTMF_DONE");
+ break;
case MSG_SET_CALL_RADIO_TECH:
int vrat = (int) msg.obj;
@@ -337,6 +332,56 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
};
+ private final Messenger mHandlerMessenger = new Messenger(mHandler);
+
+ /**
+ * The underlying telephony Connection has been redialed on a different domain (CS or IMS).
+ * Track the new telephony Connection and set back up appropriate callbacks.
+ * @param connection The new telephony Connection associated with this TelephonyConnection.
+ */
+ @VisibleForTesting
+ public void onOriginalConnectionRedialed(
+ com.android.internal.telephony.Connection connection) {
+ if (connection == null) {
+ setDisconnected(DisconnectCauseUtil
+ .toTelecomDisconnectCause(DisconnectCause.OUT_OF_NETWORK,
+ "handover failure, no connection"));
+ close();
+ return;
+ }
+ if (mOriginalConnection != null) {
+ if ((connection.getAddress() != null
+ && mOriginalConnection.getAddress() != null
+ && mOriginalConnection.getAddress().equals(connection.getAddress()))
+ || connection.getState() == mOriginalConnection.getStateBeforeHandover()) {
+ Log.i(TelephonyConnection.this, "Setting original connection after"
+ + " handover or redial, current original connection="
+ + mOriginalConnection.toString()
+ + ", new original connection="
+ + connection.toString());
+ setOriginalConnection(connection);
+ mWasImsConnection = false;
+ if (mHangupDisconnectCause != DisconnectCause.NOT_VALID) {
+ // A hangup request was initiated during the handover process, so
+ // go ahead and initiate the hangup on the new connection.
+ try {
+ Log.i(TelephonyConnection.this, "user has tried to hangup "
+ + "during handover, retrying hangup.");
+ connection.hangup();
+ } catch (CallStateException e) {
+ // Call state exception may be thrown if the connection was
+ // already disconnected, so just log this case.
+ Log.w(TelephonyConnection.this, "hangup during "
+ + "handover or redial resulted in an exception:" + e);
+ }
+ }
+ }
+ } else {
+ Log.w(TelephonyConnection.this, " mOriginalConnection==null --"
+ + " invalid state (not cleaned up)");
+ }
+ }
+
/**
* Handles {@link SuppServiceNotification}s pertinent to Telephony.
* @param ssn the notification.
@@ -506,7 +551,7 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
@Override
public void onStateChanged(android.telecom.Connection c, int state) {
- mCommunicator.onStateChanged(c, state);
+ mCommunicator.onStateChanged(c.getTelecomCallId(), state);
}
}
@@ -581,6 +626,12 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
public void onAudioQualityChanged(int audioQuality) {
mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget();
}
+
+ @Override
+ public void onMediaAttributesChanged() {
+ mHandler.obtainMessage(MSG_MEDIA_ATTRIBUTES_CHANGED).sendToTarget();
+ }
+
/**
* Handles a change in the state of conference participant(s), as reported by the
* {@link com.android.internal.telephony.Connection}.
@@ -717,6 +768,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
@Override
public void onOriginalConnectionReplaced(
com.android.internal.telephony.Connection newConnection) {
+ Log.i(TelephonyConnection.this, "onOriginalConnectionReplaced; newConn=%s",
+ newConnection);
setOriginalConnection(newConnection);
}
@@ -738,10 +791,20 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
extensionData.size());
mRtpTransport.onRtpHeaderExtensionsReceived(extensionData);
}
+
+ @Override
+ public void onReceivedDtmfDigit(char digit) {
+ if (mDtmfTransport == null) {
+ return;
+ }
+ Log.i(this, "onReceivedDtmfDigit: digit=%c", digit);
+ mDtmfTransport.onDtmfReceived(digit);
+ }
};
private TelephonyConnectionService mTelephonyConnectionService;
protected com.android.internal.telephony.Connection mOriginalConnection;
+ private Phone mPhoneForEvents;
private Call.State mConnectionState = Call.State.IDLE;
private Bundle mOriginalConnectionExtras = new Bundle();
private boolean mIsStateOverridden = false;
@@ -870,9 +933,18 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
}
+ @VisibleForTesting
+ protected TelephonyConnection() {
+ // Do nothing
+ }
+
@Override
public void onCallEvent(String event, Bundle extras) {
switch (event) {
+ case Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE:
+ // A Device to device message is being sent by a CallDiagnosticService.
+ handleOutgoingDeviceToDeviceMessage(extras);
+ break;
default:
break;
}
@@ -1127,6 +1199,64 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
@Override
+ public void onCallFilteringCompleted(CallFilteringCompletionInfo callFilteringCompletionInfo) {
+ // Check what the call screening service has to say, if it's a system dialer.
+ boolean isAllowedToDisplayPicture;
+ String callScreeningPackage =
+ callFilteringCompletionInfo.getCallScreeningComponent() == null
+ ? null
+ : callFilteringCompletionInfo.getCallScreeningComponent().getPackageName();
+ boolean isResponseFromSystemDialer =
+ Objects.equals(getPhone().getContext()
+ .getSystemService(TelecomManager.class).getSystemDialerPackage(),
+ callScreeningPackage);
+ CallScreeningService.CallResponse callScreeningResponse =
+ callFilteringCompletionInfo.getCallResponse();
+
+ if (isResponseFromSystemDialer && callScreeningResponse != null
+ && callScreeningResponse.getCallComposerAttachmentsToShow() >= 0) {
+ isAllowedToDisplayPicture = (callScreeningResponse.getCallComposerAttachmentsToShow()
+ & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PICTURE) != 0;
+ } else {
+ isAllowedToDisplayPicture = callFilteringCompletionInfo.isInContacts();
+ }
+
+ if (isImsConnection()) {
+ ImsPhone imsPhone = (getPhone() instanceof ImsPhone) ? (ImsPhone) getPhone() : null;
+ if (imsPhone != null
+ && imsPhone.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON
+ && !callFilteringCompletionInfo.isBlocked() && isAllowedToDisplayPicture) {
+ ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
+ ImsCallProfile profile = originalConnection.getImsCall().getCallProfile();
+ String serverUrl = CallComposerPictureManager.sTestMode
+ ? CallComposerPictureManager.FAKE_SERVER_URL
+ : profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL);
+ if (profile != null
+ && !TextUtils.isEmpty(serverUrl)) {
+ CallComposerPictureManager manager = CallComposerPictureManager
+ .getInstance(getPhone().getContext(), getPhone().getSubId());
+ manager.handleDownloadFromServer(new CallComposerPictureTransfer.Factory() {},
+ serverUrl,
+ (result) -> {
+ if (result.first != null) {
+ Bundle newExtras = new Bundle();
+ newExtras.putParcelable(TelecomManager.EXTRA_PICTURE_URI,
+ result.first);
+ putTelephonyExtras(newExtras);
+ } else {
+ Log.i(this, "Call composer picture download:"
+ + " error=" + result.second);
+ Bundle newExtras = new Bundle();
+ newExtras.putBoolean(TelecomManager.EXTRA_HAS_PICTURE, false);
+ putTelephonyExtras(newExtras);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ @Override
public void handleRttUpgradeResponse(RttTextStream textStream) {
if (!isImsConnection()) {
Log.w(this, "handleRttUpgradeResponse - not in IMS, so RTT cannot be enabled.");
@@ -1140,6 +1270,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
Log.v(this, "performAnswer");
if (isValidRingingCall() && getPhone() != null) {
try {
+ mTelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
+ getPhoneAccountHandle());
getPhone().acceptCall(videoState);
} catch (CallStateException e) {
Log.e(this, e, "Failed to accept call.");
@@ -1332,7 +1464,7 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO,
hasHighDefAudioProperty());
- newProperties = changeBitmask(newProperties, PROPERTY_WIFI, isWifi());
+ newProperties = changeBitmask(newProperties, PROPERTY_WIFI, isWifi() && !isCrossSimCall());
newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
isExternalConnection());
newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY,
@@ -1344,6 +1476,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
isNetworkIdentifiedEmergencyCall());
newProperties = changeBitmask(newProperties, PROPERTY_IS_ADHOC_CONFERENCE,
isAdhocConferenceCall());
+ newProperties = changeBitmask(newProperties, PROPERTY_CROSS_SIM,
+ isCrossSimCall());
if (getConnectionProperties() != newProperties) {
setTelephonyConnectionProperties(newProperties);
@@ -1403,8 +1537,29 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
// Subclass can override this to do cleanup.
}
+ public void registerForCallEvents(Phone phone) {
+ if (mPhoneForEvents == phone) {
+ Log.i(this, "registerForCallEvents - same phone requested for"
+ + "registration, ignoring.");
+ return;
+ }
+ Log.i(this, "registerForCallEvents; phone=%s", phone);
+ // Only one Phone should be registered for events at a time.
+ unregisterForCallEvents();
+ phone.registerForPreciseCallStateChanged(mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
+ phone.registerForHandoverStateChanged(mHandler, MSG_HANDOVER_STATE_CHANGED, null);
+ phone.registerForRedialConnectionChanged(mHandler, MSG_REDIAL_CONNECTION_CHANGED, null);
+ phone.registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
+ phone.registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
+ phone.registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
+ phone.registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null);
+ phone.registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null);
+ mPhoneForEvents = phone;
+ }
+
void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
- Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
+ Log.i(this, "setOriginalConnection: TelephonyConnection, originalConnection: "
+ + originalConnection);
if (mOriginalConnection != null && originalConnection != null
&& !originalConnection.isIncoming()
&& originalConnection.getOrigDialString() == null
@@ -1419,17 +1574,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
mOriginalConnectionExtras.clear();
mOriginalConnection = originalConnection;
mOriginalConnection.setTelecomCallId(getTelecomCallId());
- getPhone().registerForPreciseCallStateChanged(
- mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
- getPhone().registerForHandoverStateChanged(
- mHandler, MSG_HANDOVER_STATE_CHANGED, null);
- getPhone().registerForRedialConnectionChanged(
- mHandler, MSG_REDIAL_CONNECTION_CHANGED, null);
- getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
- getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
- getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
- getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null);
- getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null);
+ registerForCallEvents(getPhone());
+
mOriginalConnection.addPostDialListener(mPostDialListener);
mOriginalConnection.addListener(mOriginalConnectionListener);
@@ -1469,6 +1615,12 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
mIsMultiParty = mOriginalConnection.isMultiparty();
Bundle extrasToPut = new Bundle();
+ // Also stash the number verification status in a hidden extra key in the connection.
+ // We do this because a RemoteConnection DOES NOT include a getNumberVerificationStatus
+ // method and we need to be able to pass the number verification status up to Telecom
+ // despite the missing pathway in the RemoteConnectionService API surface.
+ extrasToPut.putInt(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS,
+ mOriginalConnection.getNumberVerificationStatus());
List<String> extrasToRemove = new ArrayList<>();
if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) {
extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
@@ -1587,7 +1739,7 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
Connection.AUDIO_CODEC_NONE);
if (newCodecType != oldCodecType) {
newExtras.putInt(Connection.EXTRA_AUDIO_CODEC, newCodecType);
- Log.i(this, "put audio codec:" + newCodecType);
+ Log.i(this, "refreshCodec: codec changed; old=%d, new=%d", oldCodecType, newCodecType);
changed = true;
}
if (isImsConnection()) {
@@ -1595,7 +1747,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
float oldBitrate = newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f);
if (Math.abs(newBitrate - oldBitrate) > THRESHOLD) {
newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, newBitrate);
- Log.i(this, "put audio bitrate:" + newBitrate);
+ Log.i(this, "refreshCodec: bitrate changed; old=%f, new=%f", oldBitrate,
+ newBitrate);
changed = true;
}
@@ -1604,7 +1757,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
0.0f);
if (Math.abs(newBandwidth - oldBandwidth) > THRESHOLD) {
newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ, newBandwidth);
- Log.i(this, "put audio bandwidth:" + newBandwidth);
+ Log.i(this, "refreshCodec: bandwidth changed; old=%f, new=%f", oldBandwidth,
+ newBandwidth);
changed = true;
}
} else {
@@ -1615,7 +1769,7 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
if (changed) {
- Log.i(this, "Audio attribute, Codec:"
+ Log.i(this, "refreshCodec: Codec:"
+ newExtras.getInt(Connection.EXTRA_AUDIO_CODEC, Connection.AUDIO_CODEC_NONE)
+ ", Bitrate:"
+ newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f)
@@ -1833,8 +1987,12 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
* - not indicated, then the add participant capability is same as before.
*/
if (isCapable && (mOriginalConnection != null) && !mIsMultiParty) {
- isCapable = mOriginalConnectionExtras.getBoolean(
+ // In case OEMs are still using deprecated value, read it and use it as default value.
+ boolean isCapableFromDeprecatedExtra = mOriginalConnectionExtras.getBoolean(
ImsCallProfile.EXTRA_CONFERENCE_AVAIL, isCapable);
+ isCapable = mOriginalConnectionExtras.getBoolean(
+ ImsCallProfile.EXTRA_EXTENDING_TO_CONFERENCE_SUPPORTED,
+ isCapableFromDeprecatedExtra);
}
return isCapable;
}
@@ -1948,28 +2106,43 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
/**
+ * Sets whether to treat this call as an emergency call or not.
+ * @param shouldTreatAsEmergencyCall
+ */
+ @VisibleForTesting
+ public void setShouldTreatAsEmergencyCall(boolean shouldTreatAsEmergencyCall) {
+ mTreatAsEmergencyCall = shouldTreatAsEmergencyCall;
+ }
+
+ /**
* Un-sets the underlying radio connection.
*/
void clearOriginalConnection() {
if (mOriginalConnection != null) {
- if (getPhone() != null) {
- getPhone().unregisterForPreciseCallStateChanged(mHandler);
- getPhone().unregisterForRingbackTone(mHandler);
- getPhone().unregisterForHandoverStateChanged(mHandler);
- getPhone().unregisterForRedialConnectionChanged(mHandler);
- getPhone().unregisterForDisconnect(mHandler);
- getPhone().unregisterForSuppServiceNotification(mHandler);
- getPhone().unregisterForOnHoldTone(mHandler);
- getPhone().unregisterForInCallVoicePrivacyOn(mHandler);
- getPhone().unregisterForInCallVoicePrivacyOff(mHandler);
- }
+ Log.i(this, "clearOriginalConnection; clearing=%s", mOriginalConnection);
+ unregisterForCallEvents();
mOriginalConnection.removePostDialListener(mPostDialListener);
mOriginalConnection.removeListener(mOriginalConnectionListener);
mOriginalConnection = null;
}
}
- protected void hangup(int telephonyDisconnectCode) {
+ public void unregisterForCallEvents() {
+ if (mPhoneForEvents == null) return;
+ mPhoneForEvents.unregisterForPreciseCallStateChanged(mHandler);
+ mPhoneForEvents.unregisterForRingbackTone(mHandler);
+ mPhoneForEvents.unregisterForHandoverStateChanged(mHandler);
+ mPhoneForEvents.unregisterForRedialConnectionChanged(mHandler);
+ mPhoneForEvents.unregisterForDisconnect(mHandler);
+ mPhoneForEvents.unregisterForSuppServiceNotification(mHandler);
+ mPhoneForEvents.unregisterForOnHoldTone(mHandler);
+ mPhoneForEvents.unregisterForInCallVoicePrivacyOn(mHandler);
+ mPhoneForEvents.unregisterForInCallVoicePrivacyOff(mHandler);
+ mPhoneForEvents = null;
+ }
+
+ @VisibleForTesting
+ public void hangup(int telephonyDisconnectCode) {
if (mOriginalConnection != null) {
mHangupDisconnectCause = telephonyDisconnectCode;
try {
@@ -2166,7 +2339,9 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
// If extras contain Conference support information,
// then ensure capabilities are updated.
if (mOriginalConnectionExtras.containsKey(
- ImsCallProfile.EXTRA_CONFERENCE_AVAIL)) {
+ ImsCallProfile.EXTRA_EXTENDING_TO_CONFERENCE_SUPPORTED)
+ || mOriginalConnectionExtras.containsKey(
+ ImsCallProfile.EXTRA_CONFERENCE_AVAIL)) {
updateConnectionCapabilities();
}
} else {
@@ -2277,12 +2452,18 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
+ " -> " + mHangupDisconnectCause);
disconnectCause = mHangupDisconnectCause;
}
+ ImsReasonInfo imsReasonInfo = null;
+ if (isImsConnection()) {
+ ImsPhoneConnection imsPhoneConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ imsReasonInfo = imsPhoneConnection.getImsReasonInfo();
+ }
setTelephonyConnectionDisconnected(
DisconnectCauseUtil.toTelecomDisconnectCause(
disconnectCause,
preciseDisconnectCause,
mOriginalConnection.getVendorDisconnectCause(),
- getPhone().getPhoneId()));
+ getPhone().getPhoneId(), imsReasonInfo));
close();
}
break;
@@ -2291,7 +2472,7 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
if (mCommunicator != null) {
- mCommunicator.onStateChanged(this, getState());
+ mCommunicator.onStateChanged(getTelecomCallId(), getState());
}
}
}
@@ -2430,6 +2611,16 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
/**
+ * Determines if the current connection is cross sim calling
+ */
+ private boolean isCrossSimCall() {
+ return mOriginalConnection != null
+ && mOriginalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
+ && mOriginalConnection instanceof ImsPhoneConnection
+ && ((ImsPhoneConnection) mOriginalConnection).isCrossSimCall();
+ }
+
+ /**
* Determines if the current connection is pullable.
*
* A connection is deemed to be pullable if the original connection capabilities state that it
@@ -2516,6 +2707,10 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
isLocalVideoSupported);
+ capabilities = changeBitmask(capabilities, CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT,
+ (mOriginalConnectionCapabilities & Capability.SUPPORTS_RTT_REMOTE)
+ == Capability.SUPPORTS_RTT_REMOTE);
+
return capabilities;
}
@@ -2667,8 +2862,10 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
*/
protected boolean isImsConnection() {
com.android.internal.telephony.Connection originalConnection = getOriginalConnection();
- return originalConnection != null &&
- originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
+
+ return originalConnection != null
+ && originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
+ && originalConnection instanceof ImsPhoneConnection;
}
/**
@@ -2733,7 +2930,7 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
private void updateStatusHints() {
- if (isWifi() && getPhone() != null) {
+ if (isWifi() && !isCrossSimCall() && getPhone() != null) {
int labelId = isValidRingingCall()
? R.string.status_hint_label_incoming_wifi_call
: R.string.status_hint_label_wifi_call;
@@ -2846,8 +3043,8 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
.makePstnPhoneAccountHandle(phone.getDefaultPhone())
: PhoneUtils.makePstnPhoneAccountHandle(phone);
- TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry
- .getInstance(getPhone().getContext());
+ TelecomAccountRegistry telecomAccountRegistry = getTelecomAccountRegistry(
+ getPhone().getContext());
boolean isConferencingSupported = telecomAccountRegistry
.isMergeCallSupported(phoneAccountHandle);
boolean isImsConferencingSupported = telecomAccountRegistry
@@ -2856,6 +3053,19 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
.isVideoConferencingSupported(phoneAccountHandle);
boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry
.isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle);
+ ImsCall imsCall = isImsConnection()
+ ? ((ImsPhoneConnection) getOriginalConnection()).getImsCall()
+ : null;
+ CarrierConfigManager configManager = (CarrierConfigManager) phone.getContext()
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ boolean downGradedVideoCall = false;
+ if (configManager != null) {
+ PersistableBundle config = configManager.getConfigForSubId(phone.getSubId());
+ if (config != null) {
+ downGradedVideoCall = config.getBoolean(
+ CarrierConfigManager.KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL);
+ }
+ }
Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isImsConfSupp=%b, " +
"isVidConfSupp=%b, isMergeOfWifiAllowed=%b, " +
@@ -2876,6 +3086,12 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
} else if (isVideoCall && !mIsCarrierVideoConferencingSupported) {
isConferenceSupported = false;
Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
+ } else if ((imsCall != null) && (imsCall.wasVideoCall() && downGradedVideoCall)
+ && !mIsCarrierVideoConferencingSupported) {
+ isConferenceSupported = false;
+ Log.d(this,
+ "refreshConferenceSupported = false;"
+ + " video conf not supported for downgraded audio call.");
} else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {
isConferenceSupported = false;
Log.d(this,
@@ -3043,7 +3259,7 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
}
/**
- * Set this {@link TelephonyConnection} to a held state.
+ * Set this {@link TelephonyConnection} to a disconnected state.
* <p>
* Note: This should be used instead of
* {@link #setDisconnected(android.telecom.DisconnectCause)} to ensure listeners are notified.
@@ -3193,55 +3409,87 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
private void maybeConfigureDeviceToDeviceCommunication() {
if (!getPhone().getContext().getResources().getBoolean(
R.bool.config_use_device_to_device_communication)) {
- Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
+ notifyD2DAvailabilityChanged(false);
return;
}
if (!isImsConnection()) {
- Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
+ if (mCommunicator != null) {
+ mCommunicator = null;
+ }
+ notifyD2DAvailabilityChanged(false);
return;
}
- // Implement abstracted out RTP functionality the RTP transport depends on.
- RtpAdapter rtpAdapter = new RtpAdapter() {
- @Override
- public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
- if (!isImsConnection()) {
- return Collections.EMPTY_SET;
+ if (mTreatAsEmergencyCall || mIsNetworkIdentifiedEmergencyCall) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: emergency call; no D2D");
+ notifyD2DAvailabilityChanged(false);
+ return;
+ }
+
+ ArrayList<TransportProtocol> supportedTransports = new ArrayList<>(2);
+
+ if (supportsD2DUsingRtp()) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports RTP.");
+ // Implement abstracted out RTP functionality the RTP transport depends on.
+ RtpAdapter rtpAdapter = new RtpAdapter() {
+ @Override
+ public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ return originalConnection.getAcceptedRtpHeaderExtensions();
}
- ImsPhoneConnection originalConnection =
- (ImsPhoneConnection) mOriginalConnection;
- return originalConnection.getAcceptedRtpHeaderExtensions();
- }
- @Override
- public void sendRtpHeaderExtensions(
- @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
- if (!isImsConnection()) {
- Log.w(TelephonyConnection.this, "sendRtpHeaderExtensions: not an ims conn.");
+ @Override
+ public void sendRtpHeaderExtensions(
+ @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
+ Log.i(TelephonyConnection.this, "sendRtpHeaderExtensions: sending: %s",
+ rtpHeaderExtensions.stream()
+ .map(r -> r.toString())
+ .collect(Collectors.joining(",")));
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
}
- Log.d(TelephonyConnection.this, "sendRtpHeaderExtensions: sending %d messages",
- rtpHeaderExtensions.size());
+ };
+ mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler,
+ supportsSdpNegotiationOfRtpHeaderExtensions());
+ supportedTransports.add(mRtpTransport);
+ }
+ if (supportsD2DUsingDtmf()) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports DTMF.");
+ DtmfAdapter dtmfAdapter = digit -> {
+ Log.i(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
ImsPhoneConnection originalConnection =
(ImsPhoneConnection) mOriginalConnection;
- originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
- }
- };
- mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler);
+ Message dtmfComplete = mHandler.obtainMessage(MSG_DTMF_DONE);
+ dtmfComplete.replyTo = mHandlerMessenger;
+ originalConnection.getImsCall().sendDtmf(digit, dtmfComplete);
+ };
+ ContentResolver cr = getPhone().getContext().getContentResolver();
+ mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
+ Executors.newSingleThreadScheduledExecutor());
+ supportedTransports.add(mDtmfTransport);
+ }
+ if (supportedTransports.size() > 0) {
+ mCommunicator = new Communicator(supportedTransports, this);
+ mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
+ addTelephonyConnectionListener(mD2DCallStateAdapter);
+ } else {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: no transports; disabled.");
+ notifyD2DAvailabilityChanged(false);
+ }
+ }
- DtmfAdapter dtmfAdapter = digit -> {
- if (!isImsConnection()) {
- Log.w(TelephonyConnection.this, "sendDtmf: not an ims conn.");
- }
- Log.d(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
- ImsPhoneConnection originalConnection =
- (ImsPhoneConnection) mOriginalConnection;
- originalConnection.getImsCall().sendDtmf(digit, null /* result msg not needed */);
- };
- ContentResolver cr = getPhone().getContext().getContentResolver();
- mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
- Executors.newSingleThreadScheduledExecutor());
- mCommunicator = new Communicator(List.of(mRtpTransport, mDtmfTransport), this);
- mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
- addTelephonyConnectionListener(mD2DCallStateAdapter);
+ /**
+ * Notifies upper layers of the availability of D2D communication.
+ * @param isAvailable {@code true} if D2D is available, {@code false} otherwise.
+ */
+ private void notifyD2DAvailabilityChanged(boolean isAvailable) {
+ Bundle extras = new Bundle();
+ extras.putBoolean(Connection.EXTRA_IS_DEVICE_TO_DEVICE_COMMUNICATION_AVAILABLE,
+ isAvailable);
+ putTelephonyExtras(extras);
}
/**
@@ -3262,14 +3510,57 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
// Send connection events up to Telecom so that we can relay the messages to a valid
// CallDiagnosticService which is bound.
for (Communicator.Message msg : messages) {
+ Integer dcMsgType = MessageTypeAndValueHelper.MSG_TYPE_TO_DC_MSG_TYPE.getValue(
+ msg.getType());
+ if (dcMsgType == null) {
+ // Invalid msg type, skip.
+ continue;
+ }
+
+ Integer dcMsgValue;
+ switch (msg.getType()) {
+ case CallDiagnostics.MESSAGE_CALL_AUDIO_CODEC:
+ dcMsgValue = MessageTypeAndValueHelper.CODEC_TO_DC_CODEC.getValue(
+ msg.getValue());
+ break;
+ case CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE:
+ dcMsgValue = MessageTypeAndValueHelper.RAT_TYPE_TO_DC_NETWORK_TYPE.getValue(
+ msg.getValue());
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE:
+ dcMsgValue = MessageTypeAndValueHelper.BATTERY_STATE_TO_DC_BATTERY_STATE
+ .getValue(msg.getValue());
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_NETWORK_COVERAGE:
+ dcMsgValue = MessageTypeAndValueHelper.COVERAGE_TO_DC_COVERAGE
+ .getValue(msg.getValue());
+ break;
+ default:
+ Log.w(this, "onMessagesReceived: msg=%d - invalid msg", msg.getValue());
+ continue;
+ }
+ if (dcMsgValue == null) {
+ Log.w(this, "onMessagesReceived: msg=%d/%d - invalid msg value", msg.getType(),
+ msg.getValue());
+ continue;
+ }
Bundle extras = new Bundle();
- extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, msg.getType());
- extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, msg.getValue());
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, dcMsgType);
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, dcMsgValue);
sendConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, extras);
}
}
/**
+ * Handles report from {@link Communicator} when the availability of D2D changes.
+ * @param isAvailable {@code true} if D2D is available, {@code false} if unavailable.
+ */
+ @Override
+ public void onD2DAvailabilitychanged(boolean isAvailable) {
+ notifyD2DAvailabilityChanged(isAvailable);
+ }
+
+ /**
* Called by a {@link ConnectionService} to notify Telecom that a {@link Conference#onMerge()}
* operation has started.
*/
@@ -3441,4 +3732,86 @@ abstract class TelephonyConnection extends Connection implements Holdable, Commu
return PhoneNumberUtils.stripSeparators(
PhoneNumberUtils.formatNumber(number, JAPAN_ISO_COUNTRY_CODE));
}
+
+ public TelecomAccountRegistry getTelecomAccountRegistry(Context context) {
+ return TelecomAccountRegistry.getInstance(context);
+ }
+
+ /**
+ * @return {@code true} if the carrier supports D2D using RTP header extensions, {@code false}
+ * otherwise.
+ */
+ private boolean supportsD2DUsingRtp() {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL);
+ }
+
+ /**
+ * @return {@code true} if the carrier supports D2D using DTMF digits, {@code false} otherwise.
+ */
+ private boolean supportsD2DUsingDtmf() {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL);
+ }
+
+ /**
+ * @return {@code true} if the carrier supports using SDP negotiation for the RTP header
+ * extensions used in D2D comms, {@code false} otherwise.
+ */
+ private boolean supportsSdpNegotiationOfRtpHeaderExtensions() {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager
+ .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL);
+ }
+
+ /**
+ * Handles a device to device message which a {@link CallDiagnostics} wishes to send.
+ * @param extras the call event extras bundle.
+ */
+ private void handleOutgoingDeviceToDeviceMessage(Bundle extras) {
+ int messageType = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE);
+ int messageValue = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE);
+
+ Integer internalMessageValue;
+ switch (messageType) {
+ case CallDiagnostics.MESSAGE_CALL_AUDIO_CODEC:
+ internalMessageValue = MessageTypeAndValueHelper.CODEC_TO_DC_CODEC.getKey(
+ messageValue);
+ break;
+ case CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE:
+ internalMessageValue = MessageTypeAndValueHelper.RAT_TYPE_TO_DC_NETWORK_TYPE.getKey(
+ messageValue);
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE:
+ internalMessageValue = MessageTypeAndValueHelper.BATTERY_STATE_TO_DC_BATTERY_STATE
+ .getKey(messageValue);
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_NETWORK_COVERAGE:
+ internalMessageValue = MessageTypeAndValueHelper.COVERAGE_TO_DC_COVERAGE
+ .getKey(messageValue);
+ break;
+ default:
+ Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d - invalid msg",
+ messageType);
+ return;
+ }
+ Integer internalMessageType = MessageTypeAndValueHelper.MSG_TYPE_TO_DC_MSG_TYPE.getKey(
+ messageType);
+ if (internalMessageValue == null) {
+ Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d/%d - invalid value",
+ messageType, messageValue);
+ return;
+ }
+
+ if (mCommunicator != null) {
+ Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d/%d - sending",
+ internalMessageType, internalMessageValue);
+ Set<Communicator.Message> set = new ArraySet<>();
+ set.add(new Communicator.Message(internalMessageType, internalMessageValue));
+ mCommunicator.sendMessages(set);
+ }
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index d2b0c5039..7a0d8c276 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -28,9 +28,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
+import android.os.ParcelUuid;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -70,6 +68,7 @@ import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.MMIDialogActivity;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
+import com.android.phone.callcomposer.CallComposerPictureManager;
import com.android.phone.settings.SuppServicesUiUtil;
import java.lang.ref.WeakReference;
@@ -82,9 +81,10 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -93,7 +93,7 @@ import javax.annotation.Nullable;
* Service for making GSM and CDMA connections.
*/
public class TelephonyConnectionService extends ConnectionService {
-
+ private static final String LOG_TAG = TelephonyConnectionService.class.getSimpleName();
// Timeout before we continue with the emergency call without waiting for DDS switch response
// from the modem.
private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
@@ -173,8 +173,6 @@ public class TelephonyConnectionService extends ConnectionService {
// destroyed.
@VisibleForTesting
public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
- private Handler mDdsSwitchHandler;
- private HandlerThread mHandlerThread;
private DeviceState mDeviceState = new DeviceState();
/**
@@ -361,27 +359,6 @@ public class TelephonyConnectionService extends ConnectionService {
};
/**
- * Factory for Handler creation in order to remove flakiness during t esting.
- */
- @VisibleForTesting
- public interface HandlerFactory {
- HandlerThread createHandlerThread(String name);
- Handler createHandler(Looper looper);
- }
-
- private HandlerFactory mHandlerFactory = new HandlerFactory() {
- @Override
- public HandlerThread createHandlerThread(String name) {
- return new HandlerThread(name);
- }
-
- @Override
- public Handler createHandler(Looper looper) {
- return new Handler(looper);
- }
- };
-
- /**
* DisconnectCause depends on PhoneGlobals in order to get a system context. Mock out
* dependency for testing.
*/
@@ -472,14 +449,6 @@ public class TelephonyConnectionService extends ConnectionService {
}
/**
- * Override Handler creation factory for testing.
- */
- @VisibleForTesting
- public void setHandlerFactory(HandlerFactory handlerFactory) {
- mHandlerFactory = handlerFactory;
- }
-
- /**
* Override DisconnectCause creation for testing.
*/
@VisibleForTesting
@@ -530,16 +499,11 @@ public class TelephonyConnectionService extends ConnectionService {
IntentFilter intentFilter = new IntentFilter(
TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
registerReceiver(mTtyBroadcastReceiver, intentFilter);
- mHandlerThread = mHandlerFactory.createHandlerThread("DdsSwitchHandlerThread");
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
- mDdsSwitchHandler = mHandlerFactory.createHandler(looper);
}
@Override
public boolean onUnbind(Intent intent) {
unregisterReceiver(mTtyBroadcastReceiver);
- mHandlerThread.quitSafely();
return super.onUnbind(intent);
}
@@ -873,14 +837,9 @@ public class TelephonyConnectionService extends ConnectionService {
} else {
final Connection resultConnection = getTelephonyConnection(request, numberToDial,
true, handle, phone);
- mDdsSwitchHandler.post(new Runnable() {
- @Override
- public void run() {
- boolean result = delayDialForDdsSwitch(phone);
- Log.i(this,
- "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
+ delayDialForDdsSwitch(phone, (result) -> {
+ Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
placeOutgoingConnection(request, resultConnection, phone);
- }
});
return resultConnection;
}
@@ -962,18 +921,14 @@ public class TelephonyConnectionService extends ConnectionService {
adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial,
handle, originalPhoneType, false);
} else {
- mDdsSwitchHandler.post(new Runnable() {
- @Override
- public void run() {
- boolean result = delayDialForDdsSwitch(phone);
- Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result);
- adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
- numberToDial, handle, originalPhoneType, true);
- mIsEmergencyCallPending = false;
- }
+ delayDialForDdsSwitch(phone, result -> {
+ Log.i(this, "handleOnComplete - delayDialForDdsSwitch "
+ + "result = " + result);
+ adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
+ numberToDial, handle, originalPhoneType, true);
+ mIsEmergencyCallPending = false;
});
}
-
} else {
Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
closeOrDestroyConnection(originalConnection,
@@ -1256,10 +1211,15 @@ public class TelephonyConnectionService extends ConnectionService {
createConnectionFor(phone, originalConnection, false /* isOutgoing */,
request.getAccountHandle(), request.getTelecomCallId(),
request.isAdhocConferenceCall());
+
handleIncomingRtt(request, originalConnection);
if (connection == null) {
return Connection.createCanceledConnection();
} else {
+ // Add extra to call if answering this incoming call would cause an in progress call on
+ // another subscription to be disconnected.
+ maybeIndicateAnsweringWillDisconnect(connection, request.getAccountHandle());
+
connection.setTtyEnabled(mDeviceState.isTtyModeEnabled(getApplicationContext()));
return connection;
}
@@ -1710,7 +1670,23 @@ public class TelephonyConnectionService extends ConnectionService {
return;
}
- com.android.internal.telephony.Connection originalConnection = null;
+ if (extras != null && extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) {
+ ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE);
+ CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId())
+ .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> {
+ if (uri != null) {
+ try {
+ Bundle b = new Bundle();
+ b.putParcelable(TelecomManager.EXTRA_PICTURE_URI, uri);
+ connection.putTelephonyExtras(b);
+ } catch (Exception e) {
+ Log.e(this, e, "Couldn't set picture extra on outgoing call");
+ }
+ }
+ });
+ }
+
+ final com.android.internal.telephony.Connection originalConnection;
try {
if (phone != null) {
EmergencyNumber emergencyNumber =
@@ -1756,10 +1732,16 @@ public class TelephonyConnectionService extends ConnectionService {
.setVideoState(videoState)
.setIntentExtras(extras)
.setRttTextStream(connection.getRttTextStream())
- .build());
+ .build(),
+ // We need to wait until the phone has been chosen in GsmCdmaPhone to
+ // register for the associated TelephonyConnection call event listeners.
+ connection::registerForCallEvents);
+ } else {
+ originalConnection = null;
}
} catch (CallStateException e) {
Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
+ connection.unregisterForCallEvents();
handleCallStateException(e, connection, phone);
return;
}
@@ -1787,7 +1769,17 @@ public class TelephonyConnectionService extends ConnectionService {
"Connection is null", phone.getPhoneId()));
connection.close();
} else {
- connection.setOriginalConnection(originalConnection);
+ if (!getMainThreadHandler().getLooper().isCurrentThread()) {
+ Log.w(this, "placeOriginalConnection - Unexpected, this call "
+ + "should always be on the main thread.");
+ getMainThreadHandler().post(() -> {
+ if (connection.getOriginalConnection() == null) {
+ connection.setOriginalConnection(originalConnection);
+ }
+ });
+ } else {
+ connection.setOriginalConnection(originalConnection);
+ }
}
}
@@ -1945,18 +1937,32 @@ public class TelephonyConnectionService extends ConnectionService {
/**
* If needed, block until the the default data is is switched for outgoing emergency call, or
* timeout expires.
+ * @param phone The Phone to switch the DDS on.
+ * @param completeConsumer The consumer to call once the default data subscription has been
+ * switched, provides {@code true} result if the switch happened
+ * successfully or {@code false} if the operation timed out/failed.
*/
- private boolean delayDialForDdsSwitch(Phone phone) {
+ private void delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer) {
if (phone == null) {
- return true;
+ // Do not block indefinitely.
+ completeConsumer.accept(false);
}
try {
- return possiblyOverrideDefaultDataForEmergencyCall(phone).get(
- DEFAULT_DATA_SWITCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ // Waiting for PhoneSwitcher to complete the operation.
+ CompletableFuture<Boolean> future = possiblyOverrideDefaultDataForEmergencyCall(phone);
+ // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait
+ // indefinitely for the future to complete. Instead, set a timeout that will complete
+ // the future as to not block the outgoing call indefinitely.
+ CompletableFuture<Boolean> timeout = new CompletableFuture<>();
+ phone.getContext().getMainThreadHandler().postDelayed(
+ () -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS);
+ // Also ensure that the Consumer is completed on the main thread.
+ future.acceptEitherAsync(timeout, completeConsumer,
+ phone.getContext().getMainExecutor());
} catch (Exception e) {
Log.w(this, "delayDialForDdsSwitch - exception= "
+ e.getMessage());
- return false;
+
}
}
@@ -2506,18 +2512,42 @@ public class TelephonyConnectionService extends ConnectionService {
getAllConnections().stream()
.filter(f -> f instanceof TelephonyConnection)
.forEach(t -> {
- TelephonyConnection tc = (TelephonyConnection) t;
- Communicator c = tc.getCommunicator();
- if (c == null) {
- Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled");
- return;
- }
+ TelephonyConnection tc = (TelephonyConnection) t;
+ if (!tc.isImsConnection()) {
+ Log.w(this, "sendTestDeviceToDeviceMessage: not an IMS connection");
+ return;
+ }
+ Communicator c = tc.getCommunicator();
+ if (c == null) {
+ Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled");
+ return;
+ }
+
+ c.sendMessages(new HashSet<Communicator.Message>() {{
+ add(new Communicator.Message(message, value));
+ }});
- c.sendMessages(new HashSet<Communicator.Message>() {{
- add(new Communicator.Message(message, value));
- }});
+ });
+ }
- });
+ /**
+ * Overrides the current D2D transport, forcing the specified one to be active. Used for test.
+ * @param transport The class simple name of the transport to make active.
+ */
+ public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
+ getAllConnections().stream()
+ .filter(f -> f instanceof TelephonyConnection)
+ .forEach(t -> {
+ TelephonyConnection tc = (TelephonyConnection) t;
+ Communicator c = tc.getCommunicator();
+ if (c == null) {
+ Log.w(this, "setActiveDeviceToDeviceTransport: D2D not enabled");
+ return;
+ }
+ Log.i(this, "setActiveDeviceToDeviceTransport: callId=%s, set to: %s",
+ tc.getTelecomCallId(), transport);
+ c.setTransportActive(transport);
+ });
}
private PhoneAccountHandle adjustAccountHandle(Phone phone,
@@ -2535,4 +2565,82 @@ public class TelephonyConnectionService extends ConnectionService {
}
return origAccountHandle;
}
+
+ /**
+ * For the passed in incoming {@link TelephonyConnection}, add
+ * {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} if there are ongoing calls on another
+ * subscription (ie phone account handle) than the one passed in.
+ * @param connection The connection.
+ * @param phoneAccountHandle The {@link PhoneAccountHandle} the incoming call originated on;
+ * this is passed in because
+ * {@link Connection#getPhoneAccountHandle()} is not set until after
+ * {@link ConnectionService#onCreateIncomingConnection(
+ * PhoneAccountHandle, ConnectionRequest)} returns.
+ */
+ public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection,
+ @NonNull PhoneAccountHandle phoneAccountHandle) {
+ if (isCallPresentOnOtherSub(phoneAccountHandle)) {
+ Log.i(this, "maybeIndicateAnsweringWillDisconnect; answering call %s will cause a call "
+ + "on another subscription to drop.", connection.getTelecomCallId());
+ Bundle extras = new Bundle();
+ extras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+ connection.putExtras(extras);
+ }
+ }
+
+ /**
+ * Checks to see if there are calls present on a sub other than the one passed in.
+ * @param incomingHandle The new incoming connection {@link PhoneAccountHandle}
+ */
+ private boolean isCallPresentOnOtherSub(@NonNull PhoneAccountHandle incomingHandle) {
+ return getAllConnections().stream()
+ .filter(c ->
+ // Exclude multiendpoint calls as they're not on this device.
+ (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
+ // Include any calls not on same sub as current connection.
+ && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
+ .count() > 0;
+ }
+
+ /**
+ * Where there are ongoing calls on another subscription other than the one specified,
+ * disconnect these calls. This is used where there is an incoming call on one sub, but there
+ * are ongoing calls on another sub which need to be disconnected.
+ * @param incomingHandle The incoming {@link PhoneAccountHandle}.
+ */
+ public void maybeDisconnectCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) {
+ Log.i(this, "maybeDisconnectCallsOnOtherSubs: check for calls not on %s", incomingHandle);
+ maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle);
+ }
+
+ /**
+ * Used by {@link #maybeDisconnectCallsOnOtherSubs(PhoneAccountHandle)} to perform call
+ * disconnection. This method exists as a convenience so that it is possible to unit test
+ * the core functionality.
+ * @param connections the calls to check.
+ * @param incomingHandle the incoming handle.
+ */
+ @VisibleForTesting
+ public static void maybeDisconnectCallsOnOtherSubs(@NonNull Collection<Connection> connections,
+ @NonNull PhoneAccountHandle incomingHandle) {
+ connections.stream()
+ .filter(c ->
+ // Exclude multiendpoint calls as they're not on this device.
+ (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
+ // Include any calls not on same sub as current connection.
+ && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
+ .forEach(c -> {
+ if (c instanceof TelephonyConnection) {
+ TelephonyConnection tc = (TelephonyConnection) c;
+ if (!tc.shouldTreatAsEmergencyCall()) {
+ Log.i(LOG_TAG, "maybeDisconnectCallsOnOtherSubs: disconnect %s due to "
+ + "incoming call on other sub.", tc.getTelecomCallId());
+ // Note: intentionally calling hangup instead of onDisconnect.
+ // onDisconnect posts the disconnection to a handle which means that the
+ // disconnection will take place AFTER we answer the incoming call.
+ tc.hangup(android.telephony.DisconnectCause.LOCAL);
+ }
+ }
+ });
+ }
}
diff --git a/src/com/android/services/telephony/rcs/MessageTransportWrapper.java b/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
index 0d4265ab6..45f7d95c1 100644
--- a/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
+++ b/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
@@ -482,7 +482,7 @@ public class MessageTransportWrapper implements DelegateBinderStateManager.State
}
private void logi(String log) {
- Log.w(SipTransportController.LOG_TAG, TAG + "[" + mSubId + "] " + log);
+ Log.i(SipTransportController.LOG_TAG, TAG + "[" + mSubId + "] " + log);
mLocalLog.log("[I] " + log);
}
diff --git a/src/com/android/services/telephony/rcs/SipDelegateController.java b/src/com/android/services/telephony/rcs/SipDelegateController.java
index 30edca1b0..8cc70a403 100644
--- a/src/com/android/services/telephony/rcs/SipDelegateController.java
+++ b/src/com/android/services/telephony/rcs/SipDelegateController.java
@@ -414,7 +414,7 @@ public class SipDelegateController {
}
private void logi(String log) {
- Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ Log.i(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
mLocalLog.log("[I] " + log);
}
diff --git a/src/com/android/services/telephony/rcs/SipSessionTracker.java b/src/com/android/services/telephony/rcs/SipSessionTracker.java
index 77bf3f3a7..5ab482fef 100644
--- a/src/com/android/services/telephony/rcs/SipSessionTracker.java
+++ b/src/com/android/services/telephony/rcs/SipSessionTracker.java
@@ -357,7 +357,7 @@ public class SipSessionTracker {
}
private void logi(String log) {
- Log.w(SipTransportController.LOG_TAG, TAG + ": " + log);
+ Log.i(SipTransportController.LOG_TAG, TAG + ": " + log);
mLocalLog.log("[I] " + log);
}
diff --git a/src/com/android/services/telephony/rcs/SipTransportController.java b/src/com/android/services/telephony/rcs/SipTransportController.java
index f37c3605c..709e1420f 100644
--- a/src/com/android/services/telephony/rcs/SipTransportController.java
+++ b/src/com/android/services/telephony/rcs/SipTransportController.java
@@ -803,7 +803,12 @@ public class SipTransportController implements RcsFeatureController.Feature,
}
boolean roleChanged = updateRoleCache();
if (roleChanged) {
- triggerDeregistrationEvent();
+ if (!TextUtils.isEmpty(mCachedSmsRolePackageName)) {
+ // the role change event will go A -> "" and then "" -> B. In order to not send two
+ // events to the modem, only trigger the deregistration event when we move to a
+ // non-empty package name.
+ triggerDeregistrationEvent();
+ }
// new denied tags will be picked up when reevaluate completes.
scheduleThrottledReevaluate();
}
@@ -1072,7 +1077,7 @@ public class SipTransportController implements RcsFeatureController.Feature,
}
private void logi(String log) {
- Log.w(LOG_TAG, "[" + mSlotId + "->" + mSubId + "] " + log);
+ Log.i(LOG_TAG, "[" + mSlotId + "->" + mSubId + "] " + log);
mLocalLog.log("[I] " + log);
}
diff --git a/src/com/android/services/telephony/rcs/UceControllerManager.java b/src/com/android/services/telephony/rcs/UceControllerManager.java
index 5d0cab90f..995d6851c 100644
--- a/src/com/android/services/telephony/rcs/UceControllerManager.java
+++ b/src/com/android/services/telephony/rcs/UceControllerManager.java
@@ -26,12 +26,12 @@ import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.RcsUceAdapter.PublishState;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceController;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
import java.util.List;