diff options
Diffstat (limited to 'src/com')
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; |
