[automerger skipped] Specify the component for the ACTION_SHOW_ECM_EXIT_DIALOG Intent am: 11c41d321d am: 46eb4bfb39 am: 4c46c1a119 am: 5384400e3b am: f89a32db01 am: 2804fd0079 -s ours am: b0206e5f84 -s ours am: 3eb48999b2 -s ours
am skip reason: Change-Id Id5df0b428bd7e24aa11fb9dd4c920d1019f1b389 with SHA-1 e5bd70c0c0 is in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/11401739
Change-Id: Icb375faea6dc3db7340445ed3ba09b5cef95b806
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 425440a..6040a64 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -95,6 +95,9 @@
<!-- For Vendor Debugging in Telephony -->
<protected-broadcast android:name="android.telephony.action.ANOMALY_REPORTED" />
+ <protected-broadcast android:name= "android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED" />
+ <protected-broadcast android:name= "android.intent.action.ACTION_MANAGED_ROAMING_IND" />
+
<!-- Allows granting runtime permissions to telephony related components. -->
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
@@ -238,6 +241,7 @@
android:label="@string/emergencyDialerIconLabel"
android:theme="@style/EmergencyDialerTheme"
android:screenOrientation="portrait"
+ android:exported="true"
android:resizeableActivity="false">
<intent-filter>
<action android:name="com.android.phone.EmergencyDialer.DIAL" />
@@ -265,6 +269,7 @@
android:label="@string/simContacts_title"
android:theme="@style/SimImportTheme"
android:screenOrientation="portrait"
+ android:exported="true"
android:icon="@mipmap/ic_launcher_contacts">
<intent-filter>
@@ -276,6 +281,7 @@
<activity android:name="com.android.phone.settings.fdn.FdnList"
android:label="@string/fdnListLabel"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -290,6 +296,7 @@
<activity android:name="GsmUmtsCallOptions"
android:label="@string/gsm_umts_options"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -298,6 +305,7 @@
<activity android:name="CdmaCallOptions"
android:label="@string/cdma_options"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -307,6 +315,17 @@
<activity android:name="GsmUmtsCallForwardOptions"
android:label="@string/labelCF"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
+ android:theme="@style/DialerSettingsLight">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="CdmaCallForwardOptions"
+ android:label="@string/labelCF"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -316,6 +335,7 @@
<activity android:name="GsmUmtsCallBarringOptions"
android:label="@string/labelCallBarring"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -325,6 +345,7 @@
<activity android:name="GsmUmtsAdditionalCallOptions"
android:label="@string/labelGSMMore"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -334,6 +355,7 @@
<!-- fdn setting -->
<activity android:name="com.android.phone.settings.fdn.FdnSetting"
android:label="@string/fdn"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -343,6 +365,7 @@
<!-- SIM PIN setting -->
<activity android:name="EnableIccPinScreen"
android:label="@string/enable_pin"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -352,6 +375,7 @@
<activity android:name="ChangeIccPinScreen"
android:label="@string/change_pin"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -379,6 +403,7 @@
<activity android:name="CallFeaturesSetting"
android:label="@string/call_settings"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -391,6 +416,7 @@
<!-- Activation service that trigger OTASP sim provisioning -->
<service android:name=".otasp.OtaspActivationService" android:launchMode="singleInstance"
androidprv:systemUserOnly="true"
+ android:exported="true"
android:permission="android.permission.MODIFY_PHONE_STATE">
<intent-filter>
<action android:name="android.service.simActivation.SimActivationService" />
@@ -407,6 +433,7 @@
<!-- "Accessibility" settings UI. Referenced by Dialer application. -->
<activity android:name="com.android.phone.settings.AccessibilitySettingsActivity"
android:label="@string/accessibility_settings_activity_title"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -421,6 +448,7 @@
<!-- service to dump telephony information -->
<service android:name="com.android.phone.TelephonyDebugService"
+ android:exported="true"
android:permission="android.permission.DUMP">
<intent-filter>
<action android:name="com.android.phone.TelephonyDebugService" />
@@ -430,6 +458,7 @@
<!-- Handler for EuiccManager's public-facing intents. -->
<activity android:name=".euicc.EuiccUiDispatcherActivity"
android:theme="@android:style/Theme.NoDisplay"
+ android:exported="true"
android:permission="android.permission.MODIFY_PHONE_STATE">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -450,6 +479,7 @@
EuiccController#RESOLUTION_ACTIVITY_CLASS_NAME
-->
<activity android:name=".euicc.EuiccResolutionUiDispatcherActivity"
+ android:exported="true"
android:permission="android.permission.CALL_PRIVILEGED">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -465,6 +495,7 @@
-->
<activity android:name=".euicc.EuiccPrivilegedActionUiDispatcherActivity"
android:theme="@android:style/Theme.NoDisplay"
+ android:exported="true"
android:permission="android.permission.CALL_PRIVILEGED">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -484,6 +515,7 @@
whitelisted by the underlying eUICC service implementation (i.e. the LPA).
-->
<activity android:name=".euicc.EuiccPublicActionUiDispatcherActivity"
+ android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -497,6 +529,7 @@
android:excludeFromRecents="true"
android:label="@string/ecm_exit_dialog"
android:launchMode="singleTop"
+ android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="com.android.phone.action.ACTION_SHOW_ECM_EXIT_DIALOG" />
@@ -509,13 +542,15 @@
<service android:name="com.android.services.telephony.sip.SipConnectionService"
android:label="@string/sip_connection_service_label"
android:singleUser="true"
+ android:exported="true"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
- <receiver android:name="com.android.services.telephony.sip.SipIncomingCallReceiver">
+ <receiver android:name="com.android.services.telephony.sip.SipIncomingCallReceiver"
+ android:exported="true">
<intent-filter>
<action android:name="android.net.sip.action.SIP_INCOMING_CALL" />
</intent-filter>
@@ -523,6 +558,7 @@
<activity android:name="com.android.services.telephony.sip.SipPhoneAccountSettingsActivity"
android:theme="@android:style/Theme.NoDisplay"
+ android:exported="true"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.telecom.action.CONFIGURE_PHONE_ACCOUNT" />
@@ -536,6 +572,7 @@
android:launchMode="singleTop"
android:configChanges="orientation|screenSize|keyboardHidden"
android:uiOptions="splitActionBarWhenNarrow"
+ android:exported="true"
android:parentActivityName="com.android.phone.CallFeaturesSetting" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -548,7 +585,8 @@
android:uiOptions="splitActionBarWhenNarrow">
</activity>
- <service android:name="com.android.services.telephony.sip.components.TelephonySipService">
+ <service android:name="com.android.services.telephony.sip.components.TelephonySipService"
+ android:exported="true">
<intent-filter>
<action android:name="android.net.sip.action.START_SIP" />
</intent-filter>
@@ -565,6 +603,7 @@
<activity android:name="com.android.phone.settings.PhoneAccountSettingsActivity"
android:label="@string/phone_accounts"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.telecom.action.CHANGE_PHONE_ACCOUNTS" />
@@ -576,6 +615,7 @@
android:label="@string/voicemail"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout"
android:screenOrientation="portrait"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter >
<!-- DO NOT RENAME. There are existing apps which use this string. -->
@@ -593,6 +633,7 @@
android:singleUser="true"
android:name="com.android.services.telephony.TelephonyConnectionService"
android:label="@string/pstn_connection_service_label"
+ android:exported="true"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
@@ -641,6 +682,7 @@
</intent-filter>
</service>
<service android:name="com.android.internal.telephony.dataconnection.CellularDataService"
+ android:exported="true"
android:permission="android.permission.BIND_TELEPHONY_DATA_SERVICE" >
<intent-filter>
<action android:name="android.telephony.data.DataService" />
@@ -650,6 +692,7 @@
<activity
android:name=".settings.RadioInfo"
android:label="@string/phone_info_label"
+ android:exported="true"
android:theme="@style/Theme.AppCompat.DayNight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -659,6 +702,7 @@
<activity android:name=".settings.BandMode"
android:label="@string/band_mode_title"
+ android:exported="true"
android:theme="@style/Theme.AppCompat.DayNight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e75dcb0..75b9d49 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,6 +7,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CarrierAppIntegrationTestCases"
}
]
}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1e44c72..47652e8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -273,6 +273,15 @@
<string name="sum_cfnrc_disabled">Off</string>
<!-- Error message displayed after failing to disable forwarding calls when the phone is unreachable -->
<string name="disable_cfnrc_forbidden">Your carrier doesn\'t support disabling call forwarding when your phone is unreachable.</string>
+ <string name="registration_cf_forbidden">Your carrier doesn\'t support call forwarding.</string>
+
+ <!-- Cdma Call waiting settings screen, setting option name -->
+ <string name="cdma_call_waiting">Turn on call waiting?</string>
+ <string name="enable_cdma_call_waiting_setting">During a call, you\'ll be notified about incoming calls</string>
+ <string name="enable_cdma_cw">Turn on</string>
+ <string name="disable_cdma_cw">Cancel</string>
+ <string name="cdma_call_waiting_in_ims_on">CDMA Call Waiting under IMS On</string>
+ <string name="cdma_call_waiting_in_ims_off">CDMA Call Waiting under IMS Off</string>
<!-- Title of the progress dialog displayed while updating Call settings -->
<string name="updating_title">Call settings</string>
@@ -2153,5 +2162,4 @@
<string name="carrier_provisioning">Carrier Provisioning Info</string>
<!-- Trigger Carrier Provisioning [CHAR LIMIT=NONE] -->
<string name="trigger_carrier_provisioning">Trigger Carrier Provisioning</string>
-
</resources>
diff --git a/res/xml/cdma_call_privacy.xml b/res/xml/cdma_call_privacy.xml
index 1aeeefe..a16a504 100644
--- a/res/xml/cdma_call_privacy.xml
+++ b/res/xml/cdma_call_privacy.xml
@@ -7,4 +7,14 @@
android:title="@string/voice_privacy"
android:persistent="false"
android:summary="@string/voice_privacy_summary"/>
+
+ <PreferenceScreen
+ android:key="call_forwarding_key"
+ android:title="@string/labelCF"
+ android:persistent="false" />
+
+ <com.android.phone.CdmaCallWaitingPreference
+ android:key="call_waiting_key"
+ android:title="@string/labelCW"
+ android:persistent="false" />
</PreferenceScreen>
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 54acaad..ef83ead 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -397,14 +397,10 @@
} else {
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
prefSet.removePreference(fdnButton);
-
- if (!carrierConfig.getBoolean(
- CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
- addPreferencesFromResource(R.xml.cdma_call_privacy);
- CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
- (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
- buttonVoicePrivacy.setPhone(mPhone);
- }
+ addPreferencesFromResource(R.xml.cdma_call_privacy);
+ CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
+ (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
+ buttonVoicePrivacy.setPhone(mPhone);
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
if (mPhone.getIccCard() == null || !mPhone.getIccCard().getIccFdnAvailable()) {
prefSet.removePreference(fdnButton);
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
index e8cf0d1..8e0b685 100644
--- a/src/com/android/phone/CallForwardEditPreference.java
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -122,7 +122,27 @@
Log.d(LOG_TAG, "mButtonClicked=" + mButtonClicked + ", positiveResult=" + positiveResult);
// Ignore this event if the user clicked the cancel button, or if the dialog is dismissed
// without any button being pressed (back button press or click event outside the dialog).
- if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
+ if (isUnknownStatus() && this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
+ int action = (mButtonClicked == DialogInterface.BUTTON_POSITIVE) ?
+ CommandsInterface.CF_ACTION_REGISTRATION :
+ CommandsInterface.CF_ACTION_DISABLE;
+ final String number = (action == CommandsInterface.CF_ACTION_DISABLE) ?
+ "" : getPhoneNumber();
+
+ Log.d(LOG_TAG, "reason=" + reason + ", action=" + action + ", number=" + number);
+
+ // Display no forwarding number while we're waiting for confirmation.
+ setSummaryOff("");
+
+ mPhone.setCallForwardingOption(action,
+ reason,
+ number,
+ mServiceClass,
+ 0,
+ mHandler.obtainMessage(MyHandler.MESSAGE_SET_CF,
+ action,
+ MyHandler.MESSAGE_SET_CF));
+ } else if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
int action = (isToggled() || (mButtonClicked == DialogInterface.BUTTON_POSITIVE)) ?
CommandsInterface.CF_ACTION_REGISTRATION :
CommandsInterface.CF_ACTION_DISABLE;
@@ -194,6 +214,7 @@
Log.i(LOG_TAG, "handleGetCFResponse: Overridding CF number");
}
+ setUnknownStatus(callForwardInfo.status == CommandsInterface.SS_STATUS_UNKNOWN);
setToggled(callForwardInfo.status == 1);
boolean displayVoicemailNumber = false;
if (TextUtils.isEmpty(callForwardInfo.number)) {
@@ -343,6 +364,7 @@
AsyncResult ar = (AsyncResult) msg.obj;
callForwardInfo = null;
+ boolean summaryOff = false;
if (ar.exception != null) {
Log.d(LOG_TAG, "handleGetCFResponse: ar.exception=" + ar.exception);
if (ar.exception instanceof CommandException) {
@@ -372,6 +394,8 @@
CallForwardInfo info = cfInfoArray[i];
handleCallForwardResult(info);
+ summaryOff = (info.status == CommandsInterface.SS_STATUS_UNKNOWN);
+
if (ar.userObj instanceof Throwable) {
Log.d(LOG_TAG, "Skipped duplicated error dialog");
continue;
@@ -379,8 +403,7 @@
// Show an alert if we got a success response but
// with unexpected values.
- // Currently only handle the fail-to-disable case
- // since we haven't observed fail-to-enable.
+ // Handle the fail-to-disable case.
if (msg.arg2 == MESSAGE_SET_CF &&
msg.arg1 == CommandsInterface.CF_ACTION_DISABLE &&
info.status == 1) {
@@ -404,7 +427,21 @@
}
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setNeutralButton(R.string.close_dialog, null);
- builder.setTitle(getContext().getText(R.string.error_updating_title));
+ builder.setTitle(getContext()
+ .getText(R.string.error_updating_title));
+ builder.setMessage(s);
+ builder.setCancelable(true);
+ builder.create().show();
+ } else if (msg.arg2 == MESSAGE_SET_CF &&
+ msg.arg1 == CommandsInterface.CF_ACTION_REGISTRATION &&
+ info.status == 0) {
+ // Handle the fail-to-enable case.
+ CharSequence s = getContext()
+ .getText(R.string.registration_cf_forbidden);
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ builder.setNeutralButton(R.string.close_dialog, null);
+ builder.setTitle(getContext()
+ .getText(R.string.error_updating_title));
builder.setMessage(s);
builder.setCancelable(true);
builder.create().show();
@@ -417,7 +454,15 @@
// Now whether or not we got a new number, reset our enabled
// summary text since it may have been replaced by an empty
// placeholder.
- updateSummaryText();
+ // for CDMA, doesn't display summary.
+ if (summaryOff) {
+ setSummaryOff("");
+ } else {
+ // Now whether or not we got a new number, reset our enabled
+ // summary text since it may have been replaced by an empty
+ // placeholder.
+ updateSummaryText();
+ }
}
private void handleSetCFResponse(Message msg) {
@@ -426,6 +471,16 @@
Log.d(LOG_TAG, "handleSetCFResponse: ar.exception=" + ar.exception);
// setEnabled(false);
}
+
+ if (ar.result != null) {
+ int arr = (int)ar.result;
+ if (arr == CommandsInterface.SS_STATUS_UNKNOWN) {
+ Log.d(LOG_TAG, "handleSetCFResponse: no need to re get in CDMA");
+ mTcpListener.onFinished(CallForwardEditPreference.this, false);
+ return;
+ }
+ }
+
Log.d(LOG_TAG, "handleSetCFResponse: re get");
if (!mCallForwardByUssd) {
mPhone.getCallForwardingOption(reason, mServiceClass,
diff --git a/src/com/android/phone/CdmaCallForwardOptions.java b/src/com/android/phone/CdmaCallForwardOptions.java
new file mode 100644
index 0000000..a8d2e93
--- /dev/null
+++ b/src/com/android/phone/CdmaCallForwardOptions.java
@@ -0,0 +1,254 @@
+/*
+ * 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;
+
+import android.app.ActionBar;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.util.Log;
+import android.view.MenuItem;
+
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+import java.util.ArrayList;
+
+public class CdmaCallForwardOptions extends TimeConsumingPreferenceActivity {
+ private static final String LOG_TAG = "CdmaCallForwardOptions";
+
+ private static final String NUM_PROJECTION[] = {
+ android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER
+ };
+
+ private static final String BUTTON_CFU_KEY = "button_cfu_key";
+ private static final String BUTTON_CFB_KEY = "button_cfb_key";
+ private static final String BUTTON_CFNRY_KEY = "button_cfnry_key";
+ private static final String BUTTON_CFNRC_KEY = "button_cfnrc_key";
+
+ private static final String KEY_TOGGLE = "toggle";
+ private static final String KEY_STATUS = "status";
+ private static final String KEY_NUMBER = "number";
+ private static final String KEY_ENABLE = "enable";
+
+ private CallForwardEditPreference mButtonCFU;
+ private CallForwardEditPreference mButtonCFB;
+ private CallForwardEditPreference mButtonCFNRy;
+ private CallForwardEditPreference mButtonCFNRc;
+
+ private final ArrayList<CallForwardEditPreference> mPreferences =
+ new ArrayList<CallForwardEditPreference> ();
+ private int mInitIndex= 0;
+
+ private boolean mFirstResume;
+ private Bundle mIcicle;
+ private Phone mPhone;
+ private SubscriptionInfoHelper mSubscriptionInfoHelper;
+ private boolean mReplaceInvalidCFNumbers;
+ private boolean mCallForwardByUssd;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ addPreferencesFromResource(R.xml.callforward_options);
+
+ mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, getIntent());
+ mSubscriptionInfoHelper.setActionBarTitle(
+ getActionBar(), getResources(), R.string.call_forwarding_settings_with_label);
+ mPhone = mSubscriptionInfoHelper.getPhone();
+
+ PersistableBundle b = null;
+ boolean supportCFNRc = true;
+ if (mSubscriptionInfoHelper.hasSubId()) {
+ b = PhoneGlobals.getInstance().getCarrierConfigForSubId(
+ mSubscriptionInfoHelper.getSubId());
+ } else {
+ b = PhoneGlobals.getInstance().getCarrierConfig();
+ }
+ if (b != null) {
+ mReplaceInvalidCFNumbers = b.getBoolean(
+ CarrierConfigManager.KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL);
+ mCallForwardByUssd = b.getBoolean(
+ CarrierConfigManager.KEY_USE_CALL_FORWARDING_USSD_BOOL);
+ supportCFNRc = b.getBoolean(
+ CarrierConfigManager.KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL);
+ }
+
+ PreferenceScreen prefSet = getPreferenceScreen();
+ mButtonCFU = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFU_KEY);
+ mButtonCFB = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFB_KEY);
+ mButtonCFNRy = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRY_KEY);
+ mButtonCFNRc = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRC_KEY);
+
+ mButtonCFU.setParentActivity(this, mButtonCFU.reason);
+ mButtonCFB.setParentActivity(this, mButtonCFB.reason);
+ mButtonCFNRy.setParentActivity(this, mButtonCFNRy.reason);
+ mButtonCFNRc.setParentActivity(this, mButtonCFNRc.reason);
+
+ mPreferences.add(mButtonCFU);
+ mPreferences.add(mButtonCFB);
+ mPreferences.add(mButtonCFNRy);
+
+ if (supportCFNRc) {
+ mPreferences.add(mButtonCFNRc);
+ } else {
+ // When CFNRc is not supported, mButtonCFNRc is grayed out from the menu.
+ // Default state for the preferences in this PreferenceScreen is disabled.
+ // Only preferences listed in the ArrayList mPreferences will be enabled.
+ // By not adding mButtonCFNRc to mPreferences it will be kept disabled.
+ Log.d(LOG_TAG, "onCreate: CFNRc is not supported, grey out the item.");
+ }
+
+ if (mCallForwardByUssd) {
+ //the call forwarding ussd command's behavior is similar to the call forwarding when
+ //unanswered,so only display the call forwarding when unanswered item.
+ prefSet.removePreference(mButtonCFU);
+ prefSet.removePreference(mButtonCFB);
+ prefSet.removePreference(mButtonCFNRc);
+ mPreferences.remove(mButtonCFU);
+ mPreferences.remove(mButtonCFB);
+ mPreferences.remove(mButtonCFNRc);
+ mButtonCFNRy.setDependency(null);
+ }
+
+ // we wait to do the initialization until onResume so that the
+ // TimeConsumingPreferenceActivity dialog can display as it
+ // relies on onResume / onPause to maintain its foreground state.
+
+ mFirstResume = true;
+ mIcicle = icicle;
+
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ // android.R.id.home will be triggered in onOptionsItemSelected()
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mFirstResume) {
+ if (mIcicle == null) {
+ Log.d(LOG_TAG, "start to init ");
+ CallForwardEditPreference pref = mPreferences.get(mInitIndex);
+ pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+ pref.startCallForwardOptionsQuery();
+
+ } else {
+ mInitIndex = mPreferences.size();
+
+ for (CallForwardEditPreference pref : mPreferences) {
+ Bundle bundle = mIcicle.getParcelable(pref.getKey());
+ pref.setToggled(bundle.getBoolean(KEY_TOGGLE));
+ pref.setEnabled(bundle.getBoolean(KEY_ENABLE));
+ CallForwardInfo cf = new CallForwardInfo();
+ cf.number = bundle.getString(KEY_NUMBER);
+ cf.status = bundle.getInt(KEY_STATUS);
+ pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+ pref.restoreCallForwardInfo(cf);
+ }
+ }
+ mFirstResume = false;
+ mIcicle = null;
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ for (CallForwardEditPreference pref : mPreferences) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(KEY_TOGGLE, pref.isToggled());
+ bundle.putBoolean(KEY_ENABLE, pref.isEnabled());
+ if (pref.callForwardInfo != null) {
+ bundle.putString(KEY_NUMBER, pref.callForwardInfo.number);
+ bundle.putInt(KEY_STATUS, pref.callForwardInfo.status);
+ }
+ outState.putParcelable(pref.getKey(), bundle);
+ }
+ }
+
+ @Override
+ public void onFinished(Preference preference, boolean reading) {
+ if (mInitIndex < mPreferences.size()-1 && !isFinishing()) {
+ mInitIndex++;
+ CallForwardEditPreference pref = mPreferences.get(mInitIndex);
+ pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+ pref.startCallForwardOptionsQuery();
+ }
+
+ super.onFinished(preference, reading);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d(LOG_TAG, "onActivityResult: done");
+ if (resultCode != RESULT_OK) {
+ Log.d(LOG_TAG, "onActivityResult: contact picker result not OK.");
+ return;
+ }
+ Cursor cursor = null;
+ try {
+ cursor = getContentResolver().query(data.getData(),
+ NUM_PROJECTION, null, null, null);
+ if ((cursor == null) || (!cursor.moveToFirst())) {
+ Log.d(LOG_TAG, "onActivityResult: bad contact data, no results found.");
+ return;
+ }
+
+ switch (requestCode) {
+ case CommandsInterface.CF_REASON_UNCONDITIONAL:
+ mButtonCFU.onPickActivityResult(cursor.getString(0));
+ break;
+ case CommandsInterface.CF_REASON_BUSY:
+ mButtonCFB.onPickActivityResult(cursor.getString(0));
+ break;
+ case CommandsInterface.CF_REASON_NO_REPLY:
+ mButtonCFNRy.onPickActivityResult(cursor.getString(0));
+ break;
+ case CommandsInterface.CF_REASON_NOT_REACHABLE:
+ mButtonCFNRc.onPickActivityResult(cursor.getString(0));
+ break;
+ default:
+ // TODO: may need exception here.
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ final int itemId = item.getItemId();
+ if (itemId == android.R.id.home) { // See ActionBar#setDisplayHomeAsUpEnabled()
+ CallFeaturesSetting.goUpToTopLevelSetting(this, mSubscriptionInfoHelper);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
index 8513664..2e310aa 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -19,18 +19,19 @@
import android.os.Bundle;
import android.os.PersistableBundle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.telephony.CarrierConfigManager;
import android.view.MenuItem;
import com.android.internal.telephony.PhoneConstants;
-public class CdmaCallOptions extends PreferenceActivity {
+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;
@Override
@@ -55,8 +56,15 @@
if (subInfoHelper.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
|| carrierConfig.getBoolean(CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
// disable the entire screen
- getPreferenceScreen().setEnabled(false);
+ mButtonVoicePrivacy.setEnabled(false);
}
+
+ Preference callForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
+ callForwardingPref.setIntent(subInfoHelper.getIntent(CdmaCallForwardOptions.class));
+
+ CdmaCallWaitingPreference callWaitingPref = (CdmaCallWaitingPreference)getPreferenceScreen()
+ .findPreference(CALL_WAITING_KEY);
+ callWaitingPref.init(this, subInfoHelper.getPhone());
}
@Override
@@ -76,5 +84,4 @@
}
return false;
}
-
}
diff --git a/src/com/android/phone/CdmaCallWaitingPreference.java b/src/com/android/phone/CdmaCallWaitingPreference.java
new file mode 100644
index 0000000..4cda7ba
--- /dev/null
+++ b/src/com/android/phone/CdmaCallWaitingPreference.java
@@ -0,0 +1,194 @@
+/*
+ * 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;
+
+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;
+
+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();
+
+ public CdmaCallWaitingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContext = context;
+ }
+
+ public CdmaCallWaitingPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.preferenceStyle);
+ }
+
+ public CdmaCallWaitingPreference(Context context) {
+ this(context, null);
+ }
+
+ public void init(TimeConsumingPreferenceListener listener, Phone phone) {
+ mPhone = phone;
+ mTcpListener = listener;
+ Log.d(LOG_TAG, "phone id= " + mPhone.getPhoneId());
+ mPhone.getCallWaiting(mHandler.obtainMessage(MyHandler.MESSAGE_GET_CALL_WAITING,
+ MyHandler.MESSAGE_GET_CALL_WAITING, MyHandler.MESSAGE_GET_CALL_WAITING));
+ if (mTcpListener != null) {
+ mTcpListener.onStarted(this, true);
+ }
+ }
+
+ @Override
+ public void onClick() {
+ super.onClick();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle(mContext.getText(R.string.cdma_call_waiting));
+ builder.setMessage(mContext.getText(R.string.enable_cdma_call_waiting_setting));
+ builder.setPositiveButton(R.string.enable_cdma_cw, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mPhone.setCallWaiting(true,
+ mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+ if (mTcpListener != null) {
+ mTcpListener.onStarted(CdmaCallWaitingPreference.this, false);
+ }
+ }
+ });
+ builder.setNegativeButton(R.string.disable_cdma_cw, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mPhone.setCallWaiting(false,
+ mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+ if (mTcpListener != null) {
+ mTcpListener.onStarted(CdmaCallWaitingPreference.this, false);
+ }
+ }
+ });
+ builder.create().show();
+ }
+
+ private class MyHandler extends Handler {
+ static final int MESSAGE_GET_CALL_WAITING = 0;
+ static final int MESSAGE_SET_CALL_WAITING = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_GET_CALL_WAITING:
+ handleGetCallWaitingResponse(msg);
+ break;
+ case MESSAGE_SET_CALL_WAITING:
+ handleSetCallWaitingResponse(msg);
+ break;
+ }
+ }
+
+ private void handleGetCallWaitingResponse(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+
+ if (mTcpListener != null) {
+ if (msg.arg2 == MESSAGE_SET_CALL_WAITING) {
+ mTcpListener.onFinished(CdmaCallWaitingPreference.this, false);
+ } else {
+ mTcpListener.onFinished(CdmaCallWaitingPreference.this, true);
+ }
+ }
+
+ if (ar.exception instanceof CommandException) {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleGetCallWaitingResponse: CommandException=" +
+ ar.exception);
+ }
+ if (mTcpListener != null) {
+ mTcpListener.onException(CdmaCallWaitingPreference.this,
+ (CommandException)ar.exception);
+ }
+ } else if (ar.userObj instanceof Throwable || ar.exception != null) {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception" + ar.exception);
+ }
+ if (mTcpListener != null) {
+ mTcpListener.onError(CdmaCallWaitingPreference.this,
+ TimeConsumingPreferenceActivity.RESPONSE_ERROR);
+ }
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleGetCallWaitingResponse: CW state successfully queried.");
+ }
+ int[] cwArray = (int[])ar.result;
+ if (cwArray == null) {
+ if (mTcpListener != null) {
+ mTcpListener.onError(CdmaCallWaitingPreference.this,
+ TimeConsumingPreferenceActivity.RESPONSE_ERROR);
+ }
+ return;
+ }
+
+ try {
+ if (cwArray[0] == CommandsInterface.SS_STATUS_UNKNOWN) {
+ setSummary("");
+ } else if(cwArray[0] == 1) {
+ setSummary(mContext.getString(R.string.cdma_call_waiting_in_ims_on));
+ } else if(cwArray[0] == 0) {
+ setSummary(mContext.getString(R.string.cdma_call_waiting_in_ims_off));
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ setSummary("");
+ Log.e(LOG_TAG, "handleGetCallWaitingResponse: improper result: err ="
+ + e.getMessage());
+ }
+ }
+ }
+
+ private void handleSetCallWaitingResponse(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+
+ if (ar.exception != null) {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleSetCallWaitingResponse: ar.exception=" + ar.exception);
+ }
+ }
+
+ if (ar.result != null) {
+ int arr = (int)ar.result;
+ if (arr == CommandsInterface.SS_STATUS_UNKNOWN) {
+ Log.d(LOG_TAG, "handleSetCallWaitingResponse: no need to re get in CDMA");
+ mTcpListener.onFinished(CdmaCallWaitingPreference.this, false);
+ return;
+ }
+ }
+
+ if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: re get");
+ mPhone.getCallWaiting(obtainMessage(MESSAGE_GET_CALL_WAITING,
+ MESSAGE_SET_CALL_WAITING, MESSAGE_SET_CALL_WAITING, ar.exception));
+ }
+ }
+}
diff --git a/src/com/android/phone/EditPhoneNumberPreference.java b/src/com/android/phone/EditPhoneNumberPreference.java
index 74b8a45..505c284 100644
--- a/src/com/android/phone/EditPhoneNumberPreference.java
+++ b/src/com/android/phone/EditPhoneNumberPreference.java
@@ -16,6 +16,9 @@
package com.android.phone;
+import static android.view.View.LAYOUT_DIRECTION_LOCALE;
+import static android.view.View.TEXT_DIRECTION_LOCALE;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@@ -37,6 +40,8 @@
import android.widget.ImageButton;
import android.widget.TextView;
+import com.android.internal.telephony.CommandsInterface;
+
public class EditPhoneNumberPreference extends EditTextPreference {
//allowed modes for this preference.
@@ -90,6 +95,7 @@
private String mPhoneNumber;
private boolean mChecked;
+ private boolean mIsUnknownStatus;
/**
* Interface for the dialog closed listener, related to
@@ -209,7 +215,9 @@
}
}
editText.setText(BidiFormatter.getInstance().unicodeWrap(
- mPhoneNumber, TextDirectionHeuristics.LTR));
+ mPhoneNumber, TextDirectionHeuristics.LOCALE));
+ editText.setTextDirection(TEXT_DIRECTION_LOCALE);
+ editText.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
editText.setMovementMethod(ArrowKeyMovementMethod.getInstance());
editText.setKeyListener(DialerKeyListener.getInstance());
editText.setOnFocusChangeListener(mDialogFocusChangeListener);
@@ -254,7 +262,13 @@
// displayed, since there is no need to hide the edittext
// field anymore.
if (mConfirmationMode == CM_ACTIVATION) {
- if (mChecked) {
+ if (mIsUnknownStatus) {
+ builder.setPositiveButton(mEnableText, this);
+ builder.setNeutralButton(mDisableText, this);
+ if (mPrefId == CommandsInterface.CF_REASON_ALL) {
+ builder.setPositiveButton(null, null);
+ }
+ } else if (mChecked) {
builder.setPositiveButton(mChangeNumberText, this);
builder.setNeutralButton(mDisableText, this);
} else {
@@ -310,7 +324,8 @@
@Override
public void onClick(DialogInterface dialog, int which) {
// The neutral button (button3) is always the toggle.
- if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL)) {
+ if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL)
+ && !mIsUnknownStatus) {
//flip the toggle if we are in the correct mode.
setToggled(!isToggled());
}
@@ -499,4 +514,12 @@
public void showPhoneNumberDialog() {
showDialog(null);
}
+
+ public void setUnknownStatus(boolean isUnknown) {
+ mIsUnknownStatus = isUnknown;
+ }
+
+ public boolean isUnknownStatus() {
+ return mIsUnknownStatus;
+ }
}
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index dcae24b..f5f24d3 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -28,6 +28,7 @@
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
@@ -196,6 +197,40 @@
}
}
+ @Override
+ public void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+ enforceReadPrivilegedPermission("registerUcePublishStateCallback");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+ UserCapabilityExchangeImpl.class);
+ if (uce == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support UCE.");
+ }
+ uce.registerPublishStateCallback(c);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+ enforceReadPrivilegedPermission("unregisterUcePublishStateCallback");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+ UserCapabilityExchangeImpl.class);
+ if (uce == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support UCE.");
+ }
+ uce.unregisterUcePublishStateCallback(c);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
/**
* Query for the capability of an IMS RCS service
*
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7ed7aec..d3bb176 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1881,6 +1881,11 @@
((CommandException)(ar.exception)).getCommandError()
== CommandException.Error.PASSWORD_INCORRECT) {
mResult = PhoneConstants.PIN_PASSWORD_INCORRECT;
+ } //When UiccCardApp dispose,handle message and return exception
+ else if (ar.exception instanceof CommandException &&
+ ((CommandException) (ar.exception)).getCommandError()
+ == CommandException.Error.ABORTED) {
+ mResult = PhoneConstants.PIN_OPERATION_ABORTED;
} else {
mResult = PhoneConstants.PIN_GENERAL_FAILURE;
}
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 31b7a9e..d0951e4 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -38,6 +38,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.SystemProperties;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -465,12 +466,12 @@
mQueuedWork = new ThreadPoolExecutor(1, 1, RUNNABLE_TIMEOUT_MS, TimeUnit.MICROSECONDS,
new LinkedBlockingDeque<Runnable>());
- mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
mPhone = PhoneFactory.getDefaultPhone();
+ mTelephonyManager = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE))
+ .createForSubscriptionId(mPhone.getSubId());
- mImsManager = ImsManager.getInstance(getApplicationContext(),
- SubscriptionManager.getDefaultVoicePhoneId());
+ mImsManager = ImsManager.getInstance(getApplicationContext(), mPhone.getPhoneId());
sPhoneIndexLabels = getPhoneIndexLabels(mTelephonyManager);
@@ -1465,9 +1466,9 @@
};
private boolean isImsVolteProvisioned() {
- if (mPhone != null && mImsManager != null) {
- return mImsManager.isVolteEnabledByPlatform(mPhone.getContext())
- && mImsManager.isVolteProvisionedOnDevice(mPhone.getContext());
+ if (mImsManager != null) {
+ return mImsManager.isVolteEnabledByPlatform()
+ && mImsManager.isVolteProvisionedOnDevice();
}
return false;
}
@@ -1480,9 +1481,9 @@
};
private boolean isImsVtProvisioned() {
- if (mPhone != null && mImsManager != null) {
- return mImsManager.isVtEnabledByPlatform(mPhone.getContext())
- && mImsManager.isVtProvisionedOnDevice(mPhone.getContext());
+ if (mImsManager != null) {
+ return mImsManager.isVtEnabledByPlatform()
+ && mImsManager.isVtProvisionedOnDevice();
}
return false;
}
@@ -1495,9 +1496,9 @@
};
private boolean isImsWfcProvisioned() {
- if (mPhone != null && mImsManager != null) {
- return mImsManager.isWfcEnabledByPlatform(mPhone.getContext())
- && mImsManager.isWfcProvisionedOnDevice(mPhone.getContext());
+ if (mImsManager != null) {
+ return mImsManager.isWfcEnabledByPlatform()
+ && mImsManager.isWfcProvisionedOnDevice();
}
return false;
}
@@ -1539,13 +1540,14 @@
return provisioned;
}
- private static boolean isEabEnabledByPlatform(Context context) {
- if (context != null) {
+ private boolean isEabEnabledByPlatform() {
+ if (mPhone != null) {
CarrierConfigManager configManager = (CarrierConfigManager)
- context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager != null && configManager.getConfig().getBoolean(
- CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL)) {
- return true;
+ mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+ if (b != null) {
+ return b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL,
+ false);
}
}
return false;
@@ -1562,25 +1564,25 @@
mImsVolteProvisionedSwitch.setChecked(isImsVolteProvisioned());
mImsVolteProvisionedSwitch.setOnCheckedChangeListener(mImsVolteCheckedChangeListener);
mImsVolteProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && mImsManager.isVolteEnabledByPlatform(mPhone.getContext()));
+ && mImsManager.isVolteEnabledByPlatform());
mImsVtProvisionedSwitch.setOnCheckedChangeListener(null);
mImsVtProvisionedSwitch.setChecked(isImsVtProvisioned());
mImsVtProvisionedSwitch.setOnCheckedChangeListener(mImsVtCheckedChangeListener);
mImsVtProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && mImsManager.isVtEnabledByPlatform(mPhone.getContext()));
+ && mImsManager.isVtEnabledByPlatform());
mImsWfcProvisionedSwitch.setOnCheckedChangeListener(null);
mImsWfcProvisionedSwitch.setChecked(isImsWfcProvisioned());
mImsWfcProvisionedSwitch.setOnCheckedChangeListener(mImsWfcCheckedChangeListener);
mImsWfcProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && mImsManager.isWfcEnabledByPlatform(mPhone.getContext()));
+ && mImsManager.isWfcEnabledByPlatform());
mEabProvisionedSwitch.setOnCheckedChangeListener(null);
mEabProvisionedSwitch.setChecked(isEabProvisioned());
mEabProvisionedSwitch.setOnCheckedChangeListener(mEabCheckedChangeListener);
mEabProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && isEabEnabledByPlatform(mPhone.getContext()));
+ && isEabEnabledByPlatform());
}
OnClickListener mDnsCheckButtonHandler = new OnClickListener() {
diff --git a/src/com/android/phone/settings/SuppServicesUiUtil.java b/src/com/android/phone/settings/SuppServicesUiUtil.java
index 4e9841f..4f1a79f 100644
--- a/src/com/android/phone/settings/SuppServicesUiUtil.java
+++ b/src/com/android/phone/settings/SuppServicesUiUtil.java
@@ -84,7 +84,7 @@
.create();
}
- private static String makeMessage(Context context, String preferenceKey, Phone phone) {
+ public static String makeMessage(Context context, String preferenceKey, Phone phone) {
String message = "";
int simSlot = (phone.getPhoneId() == 0) ? 1 : 2;
String suppServiceName = getSuppServiceName(context, preferenceKey);
diff --git a/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java b/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
index 92baa97..8b17cfb 100644
--- a/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
+++ b/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
@@ -64,7 +64,8 @@
resolveIntent();
- authenticatePin2();
+ // Starts PIN2 authentication only for the first time.
+ if (icicle == null) authenticatePin2();
getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.delete_fdn_contact_screen);
diff --git a/src/com/android/services/telephony/MmiCodeUtil.java b/src/com/android/services/telephony/MmiCodeUtil.java
new file mode 100644
index 0000000..d208ec3
--- /dev/null
+++ b/src/com/android/services/telephony/MmiCodeUtil.java
@@ -0,0 +1,147 @@
+/*
+ * 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.services.telephony;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class MmiCodeUtil {
+ //***** Constants
+
+ // Supp Service codes from TS 22.030 Annex B
+
+ //Called line presentation
+ static final String SC_CLIP = "30";
+ static final String SC_CLIR = "31";
+
+ // Call Forwarding
+ static final String SC_CFU = "21";
+ static final String SC_CFB = "67";
+ static final String SC_CFNRy = "61";
+ static final String SC_CFNR = "62";
+
+ static final String SC_CF_All = "002";
+ static final String SC_CF_All_Conditional = "004";
+
+ // Call Waiting
+ static final String SC_WAIT = "43";
+
+ // Call Barring
+ static final String SC_BAOC = "33";
+ static final String SC_BAOIC = "331";
+ static final String SC_BAOICxH = "332";
+ static final String SC_BAIC = "35";
+ static final String SC_BAICr = "351";
+
+ static final String SC_BA_ALL = "330";
+ static final String SC_BA_MO = "333";
+ static final String SC_BA_MT = "353";
+
+ // Supp Service Password registration
+ static final String SC_PWD = "03";
+
+ // PIN/PIN2/PUK/PUK2
+ static final String SC_PIN = "04";
+ static final String SC_PIN2 = "042";
+ static final String SC_PUK = "05";
+ static final String SC_PUK2 = "052";
+
+ // See TS 22.030 6.5.2 "Structure of the MMI"
+
+ static Pattern sPatternSuppService = Pattern.compile(
+ "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+/* 1 2 3 4 5 6 7 8 9 10 11 12
+
+ 1 = Full string up to and including #
+ 2 = action (activation/interrogation/registration/erasure)
+ 3 = service code
+ 5 = SIA
+ 7 = SIB
+ 9 = SIC
+ 10 = dialing number
+*/
+
+ static final int MATCH_GROUP_SERVICE_CODE = 3;
+
+ public static final String BUTTON_CLIR_KEY = "button_clir_key";
+ public static final String BUTTON_CW_KEY = "button_cw_key";
+ public static final String CALL_FORWARDING_KEY = "call_forwarding_key";
+ public static final String CALL_BARRING_KEY = "call_barring_key";
+
+ //***** Public Class methods
+ public static String getMmiServiceCode(String dialString) {
+ Matcher m;
+ String ret = null;
+
+ m = sPatternSuppService.matcher(dialString);
+
+ if (m.matches()) {
+ ret = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
+ }
+
+ return ret;
+ }
+
+ private static String makeEmptyNull(String s) {
+ if (s != null && s.length() == 0) return null;
+
+ return s;
+ }
+
+ static boolean isServiceCodeCallForwarding(String sc) {
+ return sc != null &&
+ (sc.equals(SC_CFU)
+ || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+ || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+ || sc.equals(SC_CF_All_Conditional));
+ }
+
+ static boolean isServiceCodeCallBarring(String sc) {
+ return sc != null &&
+ (sc.equals(SC_BAOC)
+ || sc.equals(SC_BAOIC) || sc.equals(SC_BAOICxH)
+ || sc.equals(SC_BAIC) || sc.equals(SC_BAICr)
+ || sc.equals(SC_BA_ALL) || sc.equals(SC_BA_MO)
+ || sc.equals(SC_BA_MT));
+ }
+
+ static boolean isPinPukCommand(String sc) {
+ return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
+ || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
+ }
+
+ public static String getSuppServiceKey(String dialString) {
+ String sc = getMmiServiceCode(dialString);
+ if (sc != null && sc.equals(SC_CLIP)) {
+ return "";
+ } else if (sc != null && sc.equals(SC_CLIR)) {
+ return BUTTON_CLIR_KEY;
+ } else if (isServiceCodeCallForwarding(sc)) {
+ return CALL_FORWARDING_KEY;
+ } else if (isServiceCodeCallBarring(sc)) {
+ return CALL_BARRING_KEY;
+ } else if (sc != null && sc.equals(SC_PWD)) {
+ return "";
+ } else if (sc != null && sc.equals(SC_WAIT)) {
+ return BUTTON_CW_KEY;
+ } else if (isPinPukCommand(sc)) {
+ return "";
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index b4dd050..983351f 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -117,6 +117,9 @@
private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
private static final int MSG_REJECT = 21;
+ private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
+ private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
+
private List<Uri> mParticipants;
private boolean mIsAdhocConferenceCall;
@@ -1295,6 +1298,9 @@
if (isShowingOriginalDialString()
&& mOriginalConnection.getOrigDialString() != null) {
address = getAddressFromNumber(mOriginalConnection.getOrigDialString());
+ } else if (isNeededToFormatIncomingNumberForJp()) {
+ address = getAddressFromNumber(
+ formatIncomingNumberForJp(mOriginalConnection.getAddress()));
} else {
address = getAddressFromNumber(mOriginalConnection.getAddress());
}
@@ -3201,4 +3207,29 @@
listener.onStatusHintsChanged(this, statusHints);
}
}
+
+ /**
+ * Whether the incoming call number should be formatted to national number for Japan.
+ * @return {@code true} should be convert to the national format, {@code false} otherwise.
+ */
+ private boolean isNeededToFormatIncomingNumberForJp() {
+ if (mOriginalConnection.isIncoming()
+ && !TextUtils.isEmpty(mOriginalConnection.getAddress())
+ && mOriginalConnection.getAddress().startsWith(JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN)) {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager.KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL);
+ }
+ return false;
+ }
+
+ /**
+ * Format the incoming call number to national number for Japan.
+ * @param number
+ * @return the formatted phone number (e.g, "+819012345678" -> "09012345678")
+ */
+ private String formatIncomingNumberForJp(String number) {
+ return PhoneNumberUtils.stripSeparators(
+ PhoneNumberUtils.formatNumber(number, JAPAN_ISO_COUNTRY_CODE));
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 356407a..ba3f0cc 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -17,10 +17,13 @@
package com.android.services.telephony;
import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
@@ -46,6 +49,7 @@
import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
import android.util.Pair;
+import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
@@ -65,6 +69,7 @@
import com.android.phone.MMIDialogActivity;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
+import com.android.phone.settings.SuppServicesUiUtil;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -1655,6 +1660,12 @@
? connection.getAddress().getSchemeSpecificPart()
: "";
+ if (showDataDialog(phone, number)) {
+ connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.DIALED_MMI, "UT is not available"));
+ return;
+ }
+
com.android.internal.telephony.Connection originalConnection = null;
try {
if (phone != null) {
@@ -1713,7 +1724,8 @@
if (originalConnection == null) {
int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
// On GSM phones, null connection means that we dialed an MMI code
- if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
+ phone.isUtEnabled()) {
Log.d(this, "dialed MMI code");
int subId = phone.getSubId();
Log.d(this, "subId: "+subId);
@@ -2358,6 +2370,78 @@
}
}
+ private boolean showDataDialog(Phone phone, String number) {
+ boolean ret = false;
+ final Context context = getApplicationContext();
+ String suppKey = MmiCodeUtil.getSuppServiceKey(number);
+ if (suppKey != null) {
+ boolean clirOverUtPrecautions = false;
+ boolean cfOverUtPrecautions = false;
+ boolean cbOverUtPrecautions = false;
+ boolean cwOverUtPrecautions = false;
+
+ CarrierConfigManager cfgManager = (CarrierConfigManager)
+ phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (cfgManager != null) {
+ clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL);
+ cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL);
+ cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL);
+ cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL);
+ }
+
+ boolean isSsOverUtPrecautions = SuppServicesUiUtil
+ .isSsOverUtPrecautions(context, phone);
+ if (isSsOverUtPrecautions) {
+ boolean showDialog = false;
+ if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) {
+ showDialog = true;
+ } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) {
+ showDialog = true;
+ } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) {
+ showDialog = true;
+ } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) {
+ showDialog = true;
+ }
+
+ if (showDialog) {
+ Log.d(this, "Creating UT Data enable dialog");
+ String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone);
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ DialogInterface.OnClickListener networkSettingsClickListener =
+ new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ ComponentName mobileNetworkSettingsComponent
+ = new ComponentName(
+ context.getString(
+ R.string.mobile_network_settings_package),
+ context.getString(
+ R.string.mobile_network_settings_class));
+ intent.setComponent(mobileNetworkSettingsComponent);
+ context.startActivity(intent);
+ }
+ };
+ Dialog dialog = builder.setMessage(message)
+ .setNeutralButton(context.getResources().getString(
+ R.string.settings_label),
+ networkSettingsClickListener)
+ .setPositiveButton(context.getResources().getString(
+ R.string.supp_service_over_ut_precautions_dialog_dismiss), null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ ret = true;
+ }
+ }
+ }
+ return ret;
+ }
+
/**
* Adds a {@link Conference} to the telephony ConnectionService and registers a listener for
* changes to the conference. Should be used instead of {@link #addConference(Conference)}.
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 5094c57..fcfe312 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -20,9 +20,7 @@
import android.content.Context;
import android.net.Uri;
import android.telephony.ims.ImsException;
-import android.telephony.ims.ImsRcsManager;
import android.telephony.ims.ImsReasonInfo;
-import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -131,13 +129,14 @@
try {
// May throw ImsException if for some reason the connection to the
// ImsService is gone.
+ updateConnectionStatus(manager);
setupConnectionToService(manager);
} catch (ImsException e) {
+ updateConnectionStatus(null /*manager*/);
// Use deprecated Exception for compatibility.
throw new com.android.ims.ImsException(e.getMessage(),
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
- updateConnectionStatus(manager);
}
@Override
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index c85e9a9..69d8f82 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -274,6 +274,7 @@
synchronized (mLock) {
for (int i = 0; i < mNumSlots; i++) {
RcsFeatureController f = mFeatureControllers.get(i);
+ if (f == null) continue;
pw.increaseIndent();
f.dump(fd, printWriter, args);
pw.decreaseIndent();
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
index ac8f9bf..ee0c5be 100644
--- a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
+++ b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
@@ -16,16 +16,45 @@
package com.android.services.telephony.rcs;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.telecom.TelecomManager;
+import android.telephony.AccessNetworkConstants;
+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.ImsReasonInfo;
+import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.RcsCapabilityExchange;
+import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
import android.util.Log;
import com.android.ims.RcsFeatureManager;
+import com.android.ims.RcsFeatureManager.RcsFeatureCallbacks;
import com.android.ims.ResultCode;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.R;
import com.android.service.ims.presence.ContactCapabilityResponse;
import com.android.service.ims.presence.PresenceBase;
@@ -34,7 +63,12 @@
import com.android.service.ims.presence.PresenceSubscriber;
import com.android.service.ims.presence.SubscribePublisher;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -46,12 +80,32 @@
private static final String LOG_TAG = "RcsUceImpl";
- private int mSlotId;
- private int mSubId;
+ private final int mSlotId;
+ private volatile int mSubId;
+ private volatile boolean mImsContentChangedCallbackRegistered = false;
+ // The result of requesting publish
+ private volatile int mPublishState = PresenceBase.PUBLISH_STATE_NOT_PUBLISHED;
+ // The network type which IMS registers on
+ private volatile int mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+ // The MMTel capabilities of this subscription Id
+ private MmTelFeature.MmTelCapabilities mMmTelCapabilities;
+ private final Object mCapabilitiesLock = new Object();
+ private final Context mContext;
+ private final UceImplHandler mUceImplHandler;
+ private RcsFeatureManager mRcsFeatureManager;
private final PresencePublication mPresencePublication;
private final PresenceSubscriber mPresenceSubscriber;
+ // The task Ids of updating capabilities
+ private final Set<Integer> mRequestingPublishTaskIds = new HashSet<>();
+
+ // The callbacks to notify publish state changed.
+ private final RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
+
+ // The task Ids of pending availability request.
+ private final Set<Integer> mPendingAvailabilityRequests = new HashSet<>();
+
private final ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingCapabilityRequests =
new ConcurrentHashMap<>();
@@ -60,6 +114,13 @@
mSubId = subId;
logi("created");
+ mContext = context;
+ mPublishStateCallbacks = new RemoteCallbackList<>();
+
+ HandlerThread handlerThread = new HandlerThread("UceImplHandlerThread");
+ handlerThread.start();
+ mUceImplHandler = new UceImplHandler(this, handlerThread.getLooper());
+
String[] volteError = context.getResources().getStringArray(
R.array.config_volte_provision_error_on_publish_response);
String[] rcsError = context.getResources().getStringArray(
@@ -73,13 +134,31 @@
volteError, rcsError);
onAssociatedSubscriptionUpdated(mSubId);
+ registerReceivers();
}
+ @VisibleForTesting
+ UserCapabilityExchangeImpl(Context context, int slotId, int subId, Looper looper,
+ PresencePublication presencePublication, PresenceSubscriber presenceSubscriber,
+ RemoteCallbackList<IRcsUcePublishStateCallback> publishStateCallbacks) {
+ mSlotId = slotId;
+ mSubId = subId;
+ mContext = context;
+ mPublishStateCallbacks = publishStateCallbacks;
+ mUceImplHandler = new UceImplHandler(this, looper);
+ mPresencePublication = presencePublication;
+ mPresenceSubscriber = presenceSubscriber;
+ onAssociatedSubscriptionUpdated(mSubId);
+ registerReceivers();
+ }
// Runs on main thread.
@Override
public void onRcsConnected(RcsFeatureManager rcsFeatureManager) {
logi("onRcsConnected");
+ mRcsFeatureManager = rcsFeatureManager;
+ mRcsFeatureManager.addFeatureListenerCallback(mRcsFeatureCallback);
+
mPresencePublication.updatePresencePublisher(this);
mPresenceSubscriber.updatePresenceSubscriber(this);
}
@@ -90,11 +169,22 @@
logi("onRcsDisconnected");
mPresencePublication.removePresencePublisher();
mPresenceSubscriber.removePresenceSubscriber();
+
+ if (mRcsFeatureManager != null) {
+ mRcsFeatureManager.releaseConnection();
+ mRcsFeatureManager = null;
+ }
}
// Runs on main thread.
@Override
public void onAssociatedSubscriptionUpdated(int subId) {
+ logi("onAssociatedSubscriptionUpdated: new subId=" + subId);
+
+ // Listen to the IMS content changed with new subId.
+ mUceImplHandler.registerImsContentChangedReceiver(subId);
+
+ mSubId = subId;
mPresencePublication.handleAssociatedSubscriptionChanged(subId);
mPresenceSubscriber.handleAssociatedSubscriptionChanged(subId);
}
@@ -105,6 +195,10 @@
*/
// Called on main thread.
public void onDestroy() {
+ logi("onDestroy");
+ mUceImplHandler.getLooper().quit();
+ unregisterReceivers();
+ unregisterImsProvisionCallback(mSubId);
onRcsDisconnected();
}
@@ -117,6 +211,54 @@
return toUcePublishState(publishState);
}
+ @VisibleForTesting
+ public UceImplHandler getHandler() {
+ return mUceImplHandler;
+ }
+
+ /**
+ * Register receiver to receive UCE publish state changed.
+ */
+ public void registerPublishStateCallback(IRcsUcePublishStateCallback c) {
+ synchronized (mPublishStateCallbacks) {
+ mPublishStateCallbacks.register(c);
+ }
+ }
+
+ /**
+ * Unregister UCE publish state callback.
+ */
+ public void unregisterUcePublishStateCallback(IRcsUcePublishStateCallback c) {
+ synchronized (mPublishStateCallbacks) {
+ mPublishStateCallbacks.unregister(c);
+ }
+ }
+
+ private void clearPublishStateCallbacks() {
+ synchronized (mPublishStateCallbacks) {
+ logi("clearPublishStateCallbacks");
+ final int lastIndex = mPublishStateCallbacks.getRegisteredCallbackCount() - 1;
+ for (int index = lastIndex; index >= 0; index--) {
+ IRcsUcePublishStateCallback callback =
+ mPublishStateCallbacks.getRegisteredCallbackItem(index);
+ mPublishStateCallbacks.unregister(callback);
+ }
+ }
+ }
+
+ private void notifyPublishStateChanged(@PresenceBase.PresencePublishState int state) {
+ int result = toUcePublishState(state);
+ synchronized (mPublishStateCallbacks) {
+ mPublishStateCallbacks.broadcast(c -> {
+ try {
+ c.onPublishStateChanged(result);
+ } catch (RemoteException e) {
+ logw("notifyPublishStateChanged error: " + e);
+ }
+ });
+ }
+ }
+
/**
* Perform a capabilities request and call {@link IRcsUceControllerCallback} with the result.
*/
@@ -183,43 +325,393 @@
if (taskId < 0) {
try {
c.onError(toUceError(taskId));
- return;
} catch (RemoteException e) {
logi("Calling back to dead service");
}
+ return;
}
mPendingCapabilityRequests.put(taskId, c);
}
@Override
- public int getPublisherState() {
- return 0;
+ public int requestCapability(String[] formattedContacts, int taskId) {
+ if (formattedContacts == null || formattedContacts.length == 0) {
+ logw("requestCapability error: contacts is null.");
+ return ResultCode.SUBSCRIBE_INVALID_PARAM;
+ }
+ if (mRcsFeatureManager == null) {
+ logw("requestCapability error: RcsFeatureManager is null.");
+ return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+ }
+
+ logi("requestCapability: taskId=" + taskId);
+
+ try {
+ List<Uri> contactList = Arrays.stream(formattedContacts)
+ .map(Uri::parse).collect(Collectors.toList());
+ mRcsFeatureManager.requestCapabilities(contactList, taskId);
+ } catch (Exception e) {
+ logw("requestCapability error: " + e.getMessage());
+ return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+ }
+ return ResultCode.SUCCESS;
+ }
+
+ @Override
+ public int requestAvailability(String formattedContact, int taskId) {
+ if (formattedContact == null || formattedContact.isEmpty()) {
+ logw("requestAvailability error: contact is null.");
+ return ResultCode.SUBSCRIBE_INVALID_PARAM;
+ }
+ if (mRcsFeatureManager == null) {
+ logw("requestAvailability error: RcsFeatureManager is null.");
+ return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+ }
+
+ logi("requestAvailability: taskId=" + taskId);
+ addRequestingAvailabilityTaskId(taskId);
+
+ try {
+ Uri contactUri = Uri.parse(formattedContact);
+ List<Uri> contactUris = new ArrayList<>(Arrays.asList(contactUri));
+ mRcsFeatureManager.requestCapabilities(contactUris, taskId);
+ } catch (Exception e) {
+ logw("requestAvailability error: " + e.getMessage());
+ removeRequestingAvailabilityTaskId(taskId);
+ return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+ }
+ return ResultCode.SUCCESS;
+ }
+
+ @Override
+ public int getStackStatusForCapabilityRequest() {
+ if (mRcsFeatureManager == null) {
+ logw("Check Stack status: Error! RcsFeatureManager is null.");
+ return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+ }
+
+ if (!isCapabilityDiscoveryEnabled(mSubId)) {
+ logw("Check Stack status: Error! capability discovery not enabled");
+ return ResultCode.ERROR_SERVICE_NOT_ENABLED;
+ }
+
+ if (!isEabProvisioned(mContext, mSubId)) {
+ logw("Check Stack status: Error! EAB provisioning disabled.");
+ return ResultCode.ERROR_SERVICE_NOT_ENABLED;
+ }
+
+ if (getPublisherState() != PresenceBase.PUBLISH_STATE_200_OK) {
+ logw("Check Stack status: Error! publish state " + getPublisherState());
+ return ResultCode.ERROR_SERVICE_NOT_PUBLISHED;
+ }
+ return ResultCode.SUCCESS;
+ }
+
+ /**
+ * The feature callback is to receive the request and update from RcsPresExchangeImplBase
+ */
+ @VisibleForTesting
+ public RcsFeatureCallbacks mRcsFeatureCallback = new RcsFeatureCallbacks() {
+ public void onCommandUpdate(int commandCode, int operationToken) {
+ logi("onCommandUpdate: code=" + commandCode + ", token=" + operationToken);
+ if (isPublishRequestExisted(operationToken)) {
+ onCommandUpdateForPublishRequest(commandCode, operationToken);
+ } else if (isCapabilityRequestExisted(operationToken)) {
+ onCommandUpdateForCapabilityRequest(commandCode, operationToken);
+ } else if (isAvailabilityRequestExisted(operationToken)) {
+ onCommandUpdateForAvailabilityRequest(commandCode, operationToken);
+ } else {
+ logw("onCommandUpdate: invalid token " + operationToken);
+ }
+ }
+
+ /** See {@link RcsPresenceExchangeImplBase#onNetworkResponse(int, String, int)} */
+ public void onNetworkResponse(int responseCode, String reason, int operationToken) {
+ logi("onNetworkResponse: code=" + responseCode + ", reason=" + reason
+ + ", operationToken=" + operationToken);
+ if (isPublishRequestExisted(operationToken)) {
+ onNetworkResponseForPublishRequest(responseCode, reason, operationToken);
+ } else if (isCapabilityRequestExisted(operationToken)) {
+ onNetworkResponseForCapabilityRequest(responseCode, reason, operationToken);
+ } else if (isAvailabilityRequestExisted(operationToken)) {
+ onNetworkResponseForAvailabilityRequest(responseCode, reason, operationToken);
+ } else {
+ logw("onNetworkResponse: invalid token " + operationToken);
+ }
+ }
+
+ /** See {@link RcsPresenceExchangeImplBase#onCapabilityRequestResponse(List, int)} */
+ public void onCapabilityRequestResponsePresence(List<RcsContactUceCapability> infos,
+ int operationToken) {
+ if (isAvailabilityRequestExisted(operationToken)) {
+ handleAvailabilityReqResponse(infos, operationToken);
+ } else if (isCapabilityRequestExisted(operationToken)) {
+ handleCapabilityReqResponse(infos, operationToken);
+ } else {
+ logw("capability request response: invalid token " + operationToken);
+ }
+ }
+
+ /** See {@link RcsPresenceExchangeImplBase#onNotifyUpdateCapabilites(int)} */
+ public void onNotifyUpdateCapabilities(int publishTriggerType) {
+ logi("onNotifyUpdateCapabilities: type=" + publishTriggerType);
+ mUceImplHandler.notifyUpdateCapabilities(publishTriggerType);
+ }
+
+ /** See {@link RcsPresenceExchangeImplBase#onUnpublish()} */
+ public void onUnpublish() {
+ logi("onUnpublish");
+ mUceImplHandler.unpublish();
+ }
+ };
+
+ private static class UceImplHandler extends Handler {
+ private static final int EVENT_REGISTER_IMS_CHANGED_RECEIVER = 1;
+ private static final int EVENT_NOTIFY_UPDATE_CAPABILITIES = 2;
+ private static final int EVENT_UNPUBLISH = 3;
+
+ private static final int REGISTER_IMS_CHANGED_DELAY = 10000; //10 seconds
+
+ private final WeakReference<UserCapabilityExchangeImpl> mUceImplRef;
+
+ UceImplHandler(UserCapabilityExchangeImpl uceImpl, Looper looper) {
+ super(looper);
+ mUceImplRef = new WeakReference(uceImpl);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ UserCapabilityExchangeImpl uceImpl = mUceImplRef.get();
+ if (uceImpl == null) {
+ return;
+ }
+ switch (msg.what) {
+ case EVENT_REGISTER_IMS_CHANGED_RECEIVER:
+ int subId = msg.arg1;
+ uceImpl.registerImsContentChangedReceiverInternal(subId);
+ break;
+ case EVENT_NOTIFY_UPDATE_CAPABILITIES:
+ int publishTriggerType = msg.arg1;
+ uceImpl.onNotifyUpdateCapabilities(publishTriggerType);
+ break;
+ case EVENT_UNPUBLISH:
+ uceImpl.onUnPublish();
+ break;
+ default:
+ Log.w(LOG_TAG, "handleMessage: error=" + msg.what);
+ break;
+ }
+ }
+
+ private void retryRegisteringImsContentChangedReceiver(int subId) {
+ sendRegisteringImsContentChangedMessage(subId, REGISTER_IMS_CHANGED_DELAY);
+ }
+
+ private void registerImsContentChangedReceiver(int subId) {
+ sendRegisteringImsContentChangedMessage(subId, 0);
+ }
+
+ private void sendRegisteringImsContentChangedMessage(int subId, int delay) {
+ if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return;
+ }
+ removeRegisteringImsContentChangedReceiver();
+ Message message = obtainMessage(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
+ message.arg1 = subId;
+ sendMessageDelayed(message, delay);
+ }
+
+ private void removeRegisteringImsContentChangedReceiver() {
+ removeMessages(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
+ }
+
+ private void notifyUpdateCapabilities(int publishTriggerType) {
+ Message message = obtainMessage(EVENT_NOTIFY_UPDATE_CAPABILITIES);
+ message.arg1 = publishTriggerType;
+ sendMessage(message);
+ }
+
+ private void unpublish() {
+ sendEmptyMessage(EVENT_UNPUBLISH);
+ }
+ }
+
+ private void onNotifyUpdateCapabilities(int publishTriggerType) {
+ mPresencePublication.onStackPublishRequested(publishTriggerType);
+ }
+
+ private void onUnPublish() {
+ mPresencePublication.setPublishState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
+ }
+
+ @Override
+ public @PresenceBase.PresencePublishState int getPublisherState() {
+ return mPublishState;
}
@Override
public int requestPublication(RcsContactUceCapability capabilities, String contactUri,
int taskId) {
- return 0;
+ if (mRcsFeatureManager == null) {
+ logw("requestPublication error: RcsFeatureManager is null.");
+ return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+ }
+
+ logi("requestPublication: taskId=" + taskId);
+ addPublishRequestTaskId(taskId);
+
+ try {
+ mRcsFeatureManager.requestPublication(capabilities, taskId);
+ } catch (Exception ex) {
+ logw("requestPublication error: " + ex.getMessage());
+ removePublishRequestTaskId(taskId);
+ return ResultCode.PUBLISH_GENERIC_FAILURE;
+ }
+ return ResultCode.SUCCESS;
+ }
+
+ /*
+ * Handle the callback method RcsFeatureCallbacks#onCommandUpdate(int, int)
+ */
+ private void onCommandUpdateForPublishRequest(int commandCode, int operationToken) {
+ if (!isPublishRequestExisted(operationToken)) {
+ return;
+ }
+ int resultCode = ResultCode.SUCCESS;
+ if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+ logw("onCommandUpdateForPublishRequest failed! taskId=" + operationToken
+ + ", code=" + commandCode);
+ removePublishRequestTaskId(operationToken);
+ resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+ }
+ mPresencePublication.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+ }
+
+ private void onCommandUpdateForCapabilityRequest(int commandCode, int operationToken) {
+ if (!isCapabilityRequestExisted(operationToken)) {
+ return;
+ }
+ int resultCode = ResultCode.SUCCESS;
+ if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+ logw("onCommandUpdateForCapabilityRequest failed! taskId=" + operationToken
+ + ", code=" + commandCode);
+ mPendingCapabilityRequests.remove(operationToken);
+ resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+ }
+ mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+ }
+
+ private void onCommandUpdateForAvailabilityRequest(int commandCode, int operationToken) {
+ if (!isAvailabilityRequestExisted(operationToken)) {
+ return;
+ }
+ int resultCode = ResultCode.SUCCESS;
+ if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+ logw("onCommandUpdateForAvailabilityRequest failed! taskId=" + operationToken
+ + ", code=" + commandCode);
+ removeRequestingAvailabilityTaskId(operationToken);
+ resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+ }
+ mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+ }
+
+ /*
+ * Handle the callback method RcsFeatureCallbacks#onNetworkResponse(int, String, int)
+ */
+ private void onNetworkResponseForPublishRequest(int responseCode, String reason,
+ int operationToken) {
+ if (!isPublishRequestExisted(operationToken)) {
+ return;
+ }
+ removePublishRequestTaskId(operationToken);
+ mPresencePublication.onSipResponse(operationToken, responseCode, reason);
+ }
+
+ private void onNetworkResponseForCapabilityRequest(int responseCode, String reason,
+ int operationToken) {
+ if (!isCapabilityRequestExisted(operationToken)) {
+ return;
+ }
+ mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
+ }
+
+ private void onNetworkResponseForAvailabilityRequest(int responseCode, String reason,
+ int operationToken) {
+ if (!isAvailabilityRequestExisted(operationToken)) {
+ return;
+ }
+ removeRequestingAvailabilityTaskId(operationToken);
+ mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
+ }
+
+ private void handleAvailabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
+ try {
+ if (infos == null || infos.isEmpty()) {
+ logw("handle availability request response: infos is null " + token);
+ return;
+ }
+ logi("handleAvailabilityReqResponse: token=" + token);
+ mPresenceSubscriber.updatePresence(infos.get(0));
+ } finally {
+ removeRequestingAvailabilityTaskId(token);
+ }
+ }
+
+ private void handleCapabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
+ if (infos == null) {
+ logw("handleCapabilityReqResponse: infos is null " + token);
+ mPendingCapabilityRequests.remove(token);
+ return;
+ }
+ logi("handleCapabilityReqResponse: token=" + token);
+ mPresenceSubscriber.updatePresences(token, infos, true, null);
}
@Override
- public int requestCapability(String[] formatedContacts, int taskId) {
- return 0;
+ public void updatePublisherState(@PresenceBase.PresencePublishState int publishState) {
+ logi("updatePublisherState: from " + mPublishState + " to " + publishState);
+ mPublishState = publishState;
+ notifyPublishStateChanged(publishState);
}
- @Override
- public int requestAvailability(String formattedContact, int taskId) {
- return 0;
+ private void addPublishRequestTaskId(int taskId) {
+ synchronized (mRequestingPublishTaskIds) {
+ mRequestingPublishTaskIds.add(taskId);
+ }
}
- @Override
- public int getStackStatusForCapabilityRequest() {
- return 0;
+ private void removePublishRequestTaskId(int taskId) {
+ synchronized (mRequestingPublishTaskIds) {
+ mRequestingPublishTaskIds.remove(taskId);
+ }
}
- @Override
- public void updatePublisherState(int publishState) {
+ private boolean isPublishRequestExisted(Integer taskId) {
+ synchronized (mRequestingPublishTaskIds) {
+ return mRequestingPublishTaskIds.contains(taskId);
+ }
+ }
+ private void addRequestingAvailabilityTaskId(int taskId) {
+ synchronized (mPendingAvailabilityRequests) {
+ mPendingAvailabilityRequests.contains(taskId);
+ }
+ }
+
+ private void removeRequestingAvailabilityTaskId(int taskId) {
+ synchronized (mPendingAvailabilityRequests) {
+ mPendingAvailabilityRequests.remove(taskId);
+ }
+ }
+
+ private boolean isAvailabilityRequestExisted(Integer taskId) {
+ synchronized (mPendingAvailabilityRequests) {
+ return mPendingAvailabilityRequests.contains(taskId);
+ }
+ }
+
+ private boolean isCapabilityRequestExisted(Integer taskId) {
+ return mPendingCapabilityRequests.containsKey(taskId);
}
private static String getNumberFromUri(Uri uri) {
@@ -274,6 +766,286 @@
}
}
+ /*
+ * Register receivers for updating capabilities
+ */
+ private void registerReceivers() {
+ IntentFilter filter = new IntentFilter(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
+ filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ mContext.registerReceiver(mReceiver, filter);
+
+ ContentResolver resolver = mContext.getContentResolver();
+ if (resolver != null) {
+ // Register mobile data content changed.
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.MOBILE_DATA), false,
+ mMobileDataObserver);
+
+ // Register SIM info content changed.
+ resolver.registerContentObserver(Telephony.SimInfo.CONTENT_URI, false,
+ mSimInfoContentObserver);
+ }
+ }
+
+ private void unregisterReceivers() {
+ mContext.unregisterReceiver(mReceiver);
+ ContentResolver resolver = mContext.getContentResolver();
+ if (resolver != null) {
+ resolver.unregisterContentObserver(mMobileDataObserver);
+ resolver.unregisterContentObserver(mSimInfoContentObserver);
+ }
+ }
+
+ /**
+ * Register IMS and provision content changed.
+ *
+ * Call the UceImplHandler#registerImsContentChangedReceiver instead of
+ * calling this method directly.
+ */
+ private void registerImsContentChangedReceiverInternal(int subId) {
+ mUceImplHandler.removeRegisteringImsContentChangedReceiver();
+ try {
+ final int originalSubId = mSubId;
+ if ((originalSubId == subId) && (mImsContentChangedCallbackRegistered)) {
+ logi("registerImsContentChangedReceiverInternal: already registered. skip");
+ return;
+ }
+ // Unregister original IMS and Provision callback
+ unregisterImsProvisionCallback(originalSubId);
+ // Register new IMS and Provision callback
+ registerImsProvisionCallback(subId);
+ } catch (ImsException e) {
+ logw("registerImsContentChangedReceiverInternal error: " + e);
+ mUceImplHandler.retryRegisteringImsContentChangedReceiver(subId);
+ }
+ }
+
+ private void unregisterImsProvisionCallback(int subId) {
+ if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return;
+ }
+ // Unregister IMS callback
+ ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
+ if (imsMmtelManager != null) {
+ try {
+ imsMmtelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
+ imsMmtelManager.unregisterMmTelCapabilityCallback(mCapabilityCallback);
+ } catch (RuntimeException e) {
+ logw("unregister IMS callback error: " + e.getMessage());
+ }
+ }
+
+ // Unregister provision changed callback
+ ProvisioningManager provisioningManager =
+ ProvisioningManager.createForSubscriptionId(subId);
+ try {
+ provisioningManager.unregisterProvisioningChangedCallback(mProvisioningChangedCallback);
+ } catch (RuntimeException e) {
+ logw("unregister provisioning callback error: " + e.getMessage());
+ }
+
+ // Remove all publish state callbacks
+ clearPublishStateCallbacks();
+
+ mImsContentChangedCallbackRegistered = false;
+ }
+
+ private void registerImsProvisionCallback(int subId) throws ImsException {
+ if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return;
+ }
+ // Register IMS callback
+ ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
+ if (imsMmtelManager != null) {
+ imsMmtelManager.registerImsRegistrationCallback(mContext.getMainExecutor(),
+ mImsRegistrationCallback);
+ imsMmtelManager.registerMmTelCapabilityCallback(mContext.getMainExecutor(),
+ mCapabilityCallback);
+ }
+ // Register provision changed callback
+ ProvisioningManager provisioningManager =
+ ProvisioningManager.createForSubscriptionId(subId);
+ provisioningManager.registerProvisioningChangedCallback(mContext.getMainExecutor(),
+ mProvisioningChangedCallback);
+
+ mImsContentChangedCallbackRegistered = true;
+ logi("registerImsProvisionCallback");
+ }
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) return;
+ switch (intent.getAction()) {
+ case TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED:
+ int preferredMode = intent.getIntExtra(
+ TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
+ logi("TTY preferred mode changed: " + preferredMode);
+ mPresencePublication.onTtyPreferredModeChanged(preferredMode);
+ break;
+
+ case Intent.ACTION_AIRPLANE_MODE_CHANGED:
+ boolean airplaneMode = intent.getBooleanExtra("state", false);
+ logi("Airplane mode changed: " + airplaneMode);
+ mPresencePublication.onAirplaneModeChanged(airplaneMode);
+ break;
+ }
+ }
+ };
+
+ private ContentObserver mMobileDataObserver = new ContentObserver(
+ new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean isEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.MOBILE_DATA, 1) == 1;
+ logi("Mobile data changed: enabled=" + isEnabled);
+ mPresencePublication.onMobileDataChanged(isEnabled);
+ }
+ };
+
+ private ContentObserver mSimInfoContentObserver = new ContentObserver(
+ new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (mSubId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return;
+ }
+
+ ImsMmTelManager ims = getImsMmTelManager(mSubId);
+ if (ims == null) return;
+
+ try {
+ boolean isEnabled = ims.isVtSettingEnabled();
+ logi("SimInfo changed: VT setting=" + isEnabled);
+ mPresencePublication.onVtEnabled(isEnabled);
+ } catch (RuntimeException e) {
+ logw("SimInfo changed error: " + e);
+ }
+ }
+ };
+
+ private RegistrationManager.RegistrationCallback mImsRegistrationCallback =
+ new RegistrationManager.RegistrationCallback() {
+ @Override
+ public void onRegistered(int imsTransportType) {
+ logi("onRegistered: type=" + imsTransportType);
+ mNetworkRegistrationType = imsTransportType;
+ mPresencePublication.onImsConnected();
+
+ // Also trigger PresencePublication#onFeatureCapabilityChanged method
+ MmTelFeature.MmTelCapabilities capabilities = null;
+ synchronized (mCapabilitiesLock) {
+ capabilities = mMmTelCapabilities;
+ }
+
+ if (capabilities != null) {
+ mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType,
+ capabilities);
+ }
+ }
+
+ @Override
+ public void onUnregistered(ImsReasonInfo info) {
+ logi("onUnregistered");
+ mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+
+ // Also trigger PresencePublication#onFeatureCapabilityChanged method
+ MmTelFeature.MmTelCapabilities capabilities = null;
+ synchronized (mCapabilitiesLock) {
+ capabilities = mMmTelCapabilities;
+ }
+
+ if (capabilities != null) {
+ mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType,
+ capabilities);
+ }
+ mPresencePublication.onImsDisconnected();
+ }
+ };
+
+ private ImsMmTelManager.CapabilityCallback mCapabilityCallback =
+ new ImsMmTelManager.CapabilityCallback() {
+ @Override
+ public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
+ if (capabilities == null) {
+ logw("onCapabilitiesStatusChanged: parameter is null");
+ return;
+ }
+ synchronized (mCapabilitiesLock) {
+ mMmTelCapabilities = capabilities;
+ }
+ mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType, capabilities);
+ }
+ };
+
+ private ProvisioningManager.Callback mProvisioningChangedCallback =
+ new ProvisioningManager.Callback() {
+ @Override
+ public void onProvisioningIntChanged(int item, int value) {
+ logi("onProvisioningIntChanged: item=" + item);
+ switch (item) {
+ case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS:
+ case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS:
+ case ProvisioningManager.KEY_VT_PROVISIONING_STATUS:
+ mPresencePublication.handleProvisioningChanged();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private boolean isCapabilityDiscoveryEnabled(int subId) {
+ try {
+ ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
+ int discoveryEnabled = manager.getProvisioningIntValue(
+ ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED);
+ return (discoveryEnabled == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+ } catch (Exception e) {
+ logw("isCapabilityDiscoveryEnabled error: " + e.getMessage());
+ }
+ return false;
+ }
+
+ private boolean isEabProvisioned(Context context, int subId) {
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ logw("isEabProvisioned error: invalid subscriptionId " + subId);
+ return false;
+ }
+
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle config = configManager.getConfigForSubId(subId);
+ if (config != null && !config.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL)) {
+ return true;
+ }
+ }
+
+ try {
+ ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
+ int provisioningStatus = manager.getProvisioningIntValue(
+ ProvisioningManager.KEY_EAB_PROVISIONING_STATUS);
+ return (provisioningStatus == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+ } catch (Exception e) {
+ logw("isEabProvisioned error: " + e.getMessage());
+ }
+ return false;
+ }
+
+ private ImsMmTelManager getImsMmTelManager(int subId) {
+ try {
+ ImsManager imsManager = (ImsManager) mContext.getSystemService(
+ Context.TELEPHONY_IMS_SERVICE);
+ return (imsManager == null) ? null : imsManager.getImsMmTelManager(subId);
+ } catch (IllegalArgumentException e) {
+ logw("getImsMmTelManager error: " + e.getMessage());
+ return null;
+ }
+ }
+
private void logi(String log) {
Log.i(LOG_TAG, getLogPrefix().append(log).toString());
}
diff --git a/testapps/EmbmsServiceTestApp/AndroidManifest.xml b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
index 91d8508..943fc78 100644
--- a/testapps/EmbmsServiceTestApp/AndroidManifest.xml
+++ b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
@@ -15,30 +15,31 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="com.android.phone.testapps.embmsmw"
- coreApp="true">
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.phone.testapps.embmsmw"
+ coreApp="true">
<uses-permission android:name="android.permission.SEND_EMBMS_INTENTS"/>
<application android:label="EmbmsTestMiddleware">
<service android:name="com.android.phone.testapps.embmsmw.EmbmsTestStreamingService"
- android:launchMode="singleInstance"
- androidprv:systemUserOnly="true">
+ android:launchMode="singleInstance"
+ androidprv:systemUserOnly="true"
+ android:exported="true">
<intent-filter>
- <action android:name="android.telephony.action.EmbmsStreaming" />
+ <action android:name="android.telephony.action.EmbmsStreaming"/>
</intent-filter>
</service>
<service android:name="com.android.phone.testapps.embmsmw.EmbmsSampleDownloadService"
- android:launchMode="singleInstance"
- androidprv:systemUserOnly="true">
+ android:launchMode="singleInstance"
+ androidprv:systemUserOnly="true"
+ android:exported="true">
<intent-filter>
- <action android:name="android.telephony.action.EmbmsDownload" />
+ <action android:name="android.telephony.action.EmbmsDownload"/>
</intent-filter>
</service>
<receiver android:name="com.android.phone.testapps.embmsmw.SideChannelReceiver"
- android:enabled="true"
- android:exported="true"/>
+ android:enabled="true"
+ android:exported="true"/>
</application>
</manifest>
-
diff --git a/testapps/EmbmsTestDownloadApp/AndroidManifest.xml b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
index e93cd19..640fcd1 100644
--- a/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
+++ b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
@@ -15,57 +15,54 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.embmsdownload">
+ package="com.android.phone.testapps.embmsdownload">
<application android:label="EmbmsTestDownloadApp">
- <activity
- android:name=".EmbmsTestDownloadApp"
- android:label="EmbmsDownloadFrontend">
+ <activity android:name=".EmbmsTestDownloadApp"
+ android:label="EmbmsDownloadFrontend"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- This is the receiver defined by the MBMS api. -->
- <receiver
- android:name="android.telephony.mbms.MbmsDownloadReceiver"
- android:permission="android.permission.SEND_EMBMS_INTENTS"
- android:enabled="true"
- android:exported="true">
+ <receiver android:name="android.telephony.mbms.MbmsDownloadReceiver"
+ android:permission="android.permission.SEND_EMBMS_INTENTS"
+ android:enabled="true"
+ android:exported="true">
</receiver>
<!-- This is the receiver defined by app to receive the download-done intent that was
passed into DownloadRequest. -->
- <receiver
- android:name="com.android.phone.testapps.embmsdownload.DownloadCompletionReceiver"
- android:enabled="true">
+ <receiver android:name="com.android.phone.testapps.embmsdownload.DownloadCompletionReceiver"
+ android:enabled="true">
</receiver>
<!-- This is the provider that apps must declare in their manifest. It allows the
middleware to obtain file descriptors to temp files in the app's file space -->
<!-- grantUriPermissions must be set to true -->
- <provider
- android:name="android.telephony.mbms.MbmsTempFileProvider"
- android:authorities="com.android.phone.testapps.embmsdownload"
- android:exported="false"
- android:grantUriPermissions="true">
+ <provider android:name="android.telephony.mbms.MbmsTempFileProvider"
+ android:authorities="com.android.phone.testapps.embmsdownload"
+ android:exported="false"
+ android:grantUriPermissions="true">
<!-- This is a mandatory piece of metadata that contains the directory where temp
files should be put. It should be a relative path from Context.getFilesDir() or from
Context.getExternalStorageDir(null), depending on the value of the
use-external-storage metadata. -->
- <meta-data android:name="temp-file-path" android:value="/mbms-temp/"/>
+ <meta-data android:name="temp-file-path"
+ android:value="/mbms-temp/"/>
<!-- This tells the provider whether to use the sdcard partition for the temp files or
not. -->
- <meta-data android:name="use-external-storage" android:value="false"/>
+ <meta-data android:name="use-external-storage"
+ android:value="false"/>
</provider>
<!-- This is a mandatory piece of metadata that contains the authority string for the
provider declared above -->
- <meta-data
- android:name="mbms-file-provider-authority"
- android:value="com.android.phone.testapps.embmsdownload"/>
+ <meta-data android:name="mbms-file-provider-authority"
+ android:value="com.android.phone.testapps.embmsdownload"/>
</application>
</manifest>
-
diff --git a/testapps/EmbmsTestStreamingApp/AndroidManifest.xml b/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
index d13425d..9cb83f2 100644
--- a/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
+++ b/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
@@ -15,17 +15,16 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.embmsfrontend">
+ package="com.android.phone.testapps.embmsfrontend">
<application android:label="EmbmsTestStreamingApp">
- <activity
- android:name=".EmbmsTestStreamingApp"
- android:label="EmbmsStreamingFrontend">
+ <activity android:name=".EmbmsTestStreamingApp"
+ android:label="EmbmsStreamingFrontend"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
-
diff --git a/testapps/ImsTestService/AndroidManifest.xml b/testapps/ImsTestService/AndroidManifest.xml
index eea54b8..46d0721 100644
--- a/testapps/ImsTestService/AndroidManifest.xml
+++ b/testapps/ImsTestService/AndroidManifest.xml
@@ -16,40 +16,42 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- coreApp="true"
- package="com.android.phone.testapps.imstestapp">
+ coreApp="true"
+ package="com.android.phone.testapps.imstestapp">
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!--Beware, declaring the below permission will cause the device to not boot unless you add
this app and permission to frameworks/base/data/etc/privapp-permissions-platform.xml-->
- <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
- <application
- android:label="ImsTestService"
- android:directBootAware="true">
- <activity
- android:name=".ImsTestServiceApp"
- android:label="ImsTestService">
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <application android:label="ImsTestService"
+ android:directBootAware="true">
+ <activity android:name=".ImsTestServiceApp"
+ android:label="ImsTestService"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- <activity android:name=".ImsRegistrationActivity" android:label="IMS Registration" />
- <activity android:name=".ImsCallingActivity" android:label="IMS Calling" />
- <activity android:name=".ImsConfigActivity" android:label="IMS Config" />
+ <activity android:name=".ImsRegistrationActivity"
+ android:label="IMS Registration"/>
+ <activity android:name=".ImsCallingActivity"
+ android:label="IMS Calling"/>
+ <activity android:name=".ImsConfigActivity"
+ android:label="IMS Config"/>
<service android:name=".TestImsService"
- android:exported="true"
- android:enabled="true"
- android:persistent="true"
- android:permission="android.permission.BIND_IMS_SERVICE">
- <!--meta-data android:name="android.telephony.ims.MMTEL_FEATURE" android:value="true"/-->
+ android:exported="true"
+ android:enabled="true"
+ android:persistent="true"
+ android:permission="android.permission.BIND_IMS_SERVICE">
+ <!--meta-data android:name="android.telephony.ims.MMTEL_FEATURE"
+ android:value="true"/-->
<!-- No features means we will get queried for dynamic config. -->
<intent-filter>
- <action android:name="android.telephony.ims.ImsService" />
+ <action android:name="android.telephony.ims.ImsService"/>
</intent-filter>
</service>
</application>
</manifest>
-
diff --git a/testapps/SmsManagerTestApp/AndroidManifest.xml b/testapps/SmsManagerTestApp/AndroidManifest.xml
index c5f4621..57b7344 100644
--- a/testapps/SmsManagerTestApp/AndroidManifest.xml
+++ b/testapps/SmsManagerTestApp/AndroidManifest.xml
@@ -16,29 +16,30 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.smsmanagertestapp">
- <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="29" />
+ package="com.android.phone.testapps.smsmanagertestapp">
+ <uses-sdk android:minSdkVersion="24"
+ android:targetSdkVersion="29"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application android:label="SmsManagerTestApp">
- <activity
- android:name=".SmsManagerTestApp"
- android:label="SmsManagerTestApp">
+ <activity android:name=".SmsManagerTestApp"
+ android:label="SmsManagerTestApp"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- <service android:name=".SmsManagerTestService" android:exported="false" />
+ <service android:name=".SmsManagerTestService"
+ android:exported="false"/>
<receiver android:name=".SendStatusReceiver"
- android:exported="false">
+ android:exported="false">
<intent-filter>
- <action android:name="com.android.phone.testapps.smsmanagertestapp.message_sent_action" />
- <data android:scheme="content" />
+ <action android:name="com.android.phone.testapps.smsmanagertestapp.message_sent_action"/>
+ <data android:scheme="content"/>
</intent-filter>
</receiver>
</application>
</manifest>
-
diff --git a/testapps/TelephonyManagerTestApp/AndroidManifest.xml b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
index 044d0b2..40fc549 100644
--- a/testapps/TelephonyManagerTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.telephonymanagertestapp">
+ package="com.android.phone.testapps.telephonymanagertestapp">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
@@ -26,31 +26,29 @@
<uses-permission android:name="android.permission.CALL_PRIVILEGED"/>
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
- android.Manifest.permission.ACCESS_FINE_LOCATION
+ android.Manifest.permission.ACCESS_FINE_LOCATION
<application android:label="TelephonyManagerTestApp">
- <activity
- android:name=".TelephonyManagerTestApp"
- android:label="TelephonyManagerTestApp">
+ <activity android:name=".TelephonyManagerTestApp"
+ android:label="TelephonyManagerTestApp"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <action android:name="android.intent.action.SEARCH" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <action android:name="android.intent.action.SEARCH"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
- <meta-data
- android:name="android.app.searchable"
- android:resource="@xml/searchable">
+ <meta-data android:name="android.app.searchable"
+ android:resource="@xml/searchable">
</meta-data>
</activity>
- <activity
- android:name=".CallingMethodActivity"
- android:label="CallingMethodActivity">
+ <activity android:name=".CallingMethodActivity"
+ android:label="CallingMethodActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
-
diff --git a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
index 550c9f0..7432156 100644
--- a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
@@ -15,24 +15,23 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.telephonyregistry">
+ package="com.android.phone.testapps.telephonyregistry">
<uses-sdk android:minSdkVersion="25"
- android:targetSdkVersion="25"/>
+ android:targetSdkVersion="25"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE"/>
<application android:label="TelephonyRegistryTestApp">
- <activity
- android:name=".TelephonyRegistryTestApp"
- android:label="TelephonyRegistryTestApp">
+ <activity android:name=".TelephonyRegistryTestApp"
+ android:label="TelephonyRegistryTestApp"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
-
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index d434650..174d22e 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -31,6 +31,7 @@
adb shell am start -n com.android.phone.tests/.CallDialTest
-->
<activity android:name="CallDialTest"
+ android:exported="true"
android:label="@string/callDialTestLabel">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -39,6 +40,7 @@
</activity>
<service android:name="SendInstantTextTestService"
+ android:exported="true"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index c5b9b1e..13bfe3b 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.doReturn;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -29,6 +30,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
import android.test.mock.MockContext;
import org.mockito.Mock;
@@ -42,6 +44,7 @@
@Mock TelecomManager mMockTelecomManager;
@Mock TelephonyManager mMockTelephonyManager;
@Mock SubscriptionManager mMockSubscriptionManager;
+ @Mock ImsManager mMockImsManager;
private PersistableBundle mCarrierConfig = new PersistableBundle();
@@ -94,6 +97,11 @@
}
@Override
+ public ContentResolver getContentResolver() {
+ return null;
+ }
+
+ @Override
public Object getSystemService(String name) {
switch (name) {
case (Context.CARRIER_CONFIG_SERVICE) : {
@@ -108,6 +116,9 @@
case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
return mMockSubscriptionManager;
}
+ case(Context.TELEPHONY_IMS_SERVICE) : {
+ return mMockImsManager;
+ }
}
return null;
}
diff --git a/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
new file mode 100644
index 0000000..82ecd79
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.services.telephony.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.RemoteCallbackList;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.stub.RcsCapabilityExchange;
+import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.RcsFeatureManager.RcsFeatureCallbacks;
+import com.android.ims.ResultCode;
+import com.android.service.ims.presence.PresenceBase;
+import com.android.service.ims.presence.PresencePublication;
+import com.android.service.ims.presence.PresencePublisher;
+import com.android.service.ims.presence.PresenceSubscriber;
+import com.android.service.ims.presence.SubscribePublisher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class UserCapabilityExchangeImplTest extends TelephonyTestBase {
+
+ private int mSlotId = 0;
+ private int mSubId = 1;
+ private int mUpdatedSubId = 2;
+
+ @Captor ArgumentCaptor<IRcsUcePublishStateCallback> mPublishStateCallbacksCaptor;
+
+ @Mock PresencePublication mPresencePublication;
+ @Mock PresenceSubscriber mPresenceSubscriber;
+ @Mock RcsFeatureManager mRcsFeatureManager;
+ @Mock ImsMmTelManager mImsMmTelManager;
+ @Mock RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
+
+ private Looper mLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ ImsManager imsManager =
+ (ImsManager) mContext.getSystemService(Context.TELEPHONY_IMS_SERVICE);
+ when(imsManager.getImsMmTelManager(mSubId)).thenReturn(mImsMmTelManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ if (mLooper != null) {
+ mLooper.quit();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ public void testServiceConnected() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ verify(mRcsFeatureManager).addFeatureListenerCallback(any(RcsFeatureCallbacks.class));
+ verify(mPresencePublication).updatePresencePublisher(any(PresencePublisher.class));
+ verify(mPresenceSubscriber).updatePresenceSubscriber(any(SubscribePublisher.class));
+ }
+
+ @Test
+ public void testServiceDisconnected() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsDisconnected();
+
+ verify(mPresencePublication).removePresencePublisher();
+ verify(mPresenceSubscriber).removePresenceSubscriber();
+ }
+
+ @Test
+ public void testSubscriptionUpdated() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onAssociatedSubscriptionUpdated(mUpdatedSubId);
+
+ verify(mImsMmTelManager).registerImsRegistrationCallback(any(Executor.class),
+ any(RegistrationManager.RegistrationCallback.class));
+ verify(mImsMmTelManager).registerMmTelCapabilityCallback(any(Executor.class),
+ any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mPresencePublication).handleAssociatedSubscriptionChanged(mUpdatedSubId);
+ verify(mPresenceSubscriber).handleAssociatedSubscriptionChanged(mUpdatedSubId);
+ }
+
+ @Test
+ public void testUcePublishStateRetrieval() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.getUcePublishState();
+
+ verify(mPresencePublication).getPublishState();
+ }
+
+ @Test
+ public void testRegisterPublishStateCallbacks() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.registerPublishStateCallback(any(IRcsUcePublishStateCallback.class));
+ verify(mPublishStateCallbacks).register(mPublishStateCallbacksCaptor.capture());
+ }
+
+ @Test
+ public void testOnNotifyUpdateCapabilities() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ int triggerType = RcsPresenceExchangeImplBase.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN;
+ uceImpl.mRcsFeatureCallback.onNotifyUpdateCapabilities(triggerType);
+ waitForMs(1000);
+
+ verify(mPresencePublication).onStackPublishRequested(triggerType);
+ }
+
+ @Test
+ public void testRequestPublicationWithSuccessfulResponse() throws Exception {
+ int taskId = 1;
+ int sipResponse = 200;
+ Uri contact = Uri.fromParts("sip", "test", null);
+ RcsContactUceCapability.Builder builder = new RcsContactUceCapability.Builder(contact);
+ RcsContactUceCapability capability = builder.build();
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ doAnswer(invocation -> {
+ uceImpl.mRcsFeatureCallback.onCommandUpdate(RcsCapabilityExchange.COMMAND_CODE_SUCCESS,
+ taskId);
+ uceImpl.mRcsFeatureCallback.onNetworkResponse(sipResponse, null, taskId);
+ return null;
+ }).when(mRcsFeatureManager).requestPublication(capability, taskId);
+
+ // Request publication
+ int result = uceImpl.requestPublication(capability, contact.toString(), taskId);
+
+ assertEquals(ResultCode.SUCCESS, result);
+ verify(mPresencePublication).onCommandStatusUpdated(taskId, taskId, ResultCode.SUCCESS);
+ verify(mPresencePublication).onSipResponse(taskId, sipResponse, null);
+ }
+
+ @Test
+ public void testRequestPublicationWithFailedResponse() throws Exception {
+ int taskId = 1;
+ Uri contact = Uri.fromParts("sip", "test", null);
+ RcsContactUceCapability.Builder builder = new RcsContactUceCapability.Builder(contact);
+ RcsContactUceCapability capability = builder.build();
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ doAnswer(invocation -> {
+ uceImpl.mRcsFeatureCallback.onCommandUpdate(
+ RcsCapabilityExchange.COMMAND_CODE_GENERIC_FAILURE, taskId);
+ return null;
+ }).when(mRcsFeatureManager).requestPublication(capability, taskId);
+
+ // Request publication
+ int result = uceImpl.requestPublication(capability, contact.toString(), taskId);
+
+ assertEquals(ResultCode.SUCCESS, result);
+ verify(mPresencePublication).onCommandStatusUpdated(taskId, taskId,
+ ResultCode.PUBLISH_GENERIC_FAILURE);
+ }
+
+ @Test
+ public void testRequestCapability() throws Exception {
+ int taskId = 1;
+ int sipResponse = 200;
+ List<RcsContactUceCapability> infos = new ArrayList<>();
+ List<Uri> contacts = Arrays.asList(Uri.fromParts("sip", "00000", null));
+ IRcsUceControllerCallback callback = Mockito.mock(IRcsUceControllerCallback.class);
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ when(mPresenceSubscriber.requestCapability(anyList(), any())).thenReturn(taskId);
+
+ doAnswer(invocation -> {
+ uceImpl.mRcsFeatureCallback.onCommandUpdate(RcsCapabilityExchange.COMMAND_CODE_SUCCESS,
+ taskId);
+ uceImpl.mRcsFeatureCallback.onNetworkResponse(sipResponse, null, taskId);
+ uceImpl.mRcsFeatureCallback.onCapabilityRequestResponsePresence(infos, taskId);
+ return null;
+ }).when(mRcsFeatureManager).requestCapabilities(anyList(), anyInt());
+
+ uceImpl.requestCapabilities(contacts, callback);
+ uceImpl.requestCapability(new String[] {"00000"}, taskId);
+
+ verify(mPresenceSubscriber).onCommandStatusUpdated(taskId, taskId, ResultCode.SUCCESS);
+ verify(mPresenceSubscriber).onSipResponse(taskId, sipResponse, null);
+ verify(mPresenceSubscriber).updatePresences(taskId, infos, true, null);
+ }
+
+ @Test
+ public void testUpdatePublisherState() throws Exception {
+ IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+ doAnswer(invocation -> {
+ callback.onPublishStateChanged(anyInt());
+ return null;
+ }).when(mPublishStateCallbacks).broadcast(any());
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+ uceImpl.registerPublishStateCallback(callback);
+ uceImpl.updatePublisherState(PresenceBase.PUBLISH_STATE_200_OK);
+
+ assertEquals(PresenceBase.PUBLISH_STATE_200_OK, uceImpl.getPublisherState());
+ verify(callback).onPublishStateChanged(anyInt());
+ }
+
+ @Test
+ public void testUnpublish() throws Exception {
+ IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+ doAnswer(invocation -> {
+ callback.onPublishStateChanged(anyInt());
+ return null;
+ }).when(mPublishStateCallbacks).broadcast(any());
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+ uceImpl.mRcsFeatureCallback.onUnpublish();
+ waitForMs(1000);
+
+ verify(mPresencePublication).setPublishState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
+ }
+
+ private UserCapabilityExchangeImpl createUserCapabilityExchangeImpl() throws Exception {
+ HandlerThread handlerThread = new HandlerThread("UceImplHandlerThread");
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ UserCapabilityExchangeImpl uceImpl = new UserCapabilityExchangeImpl(mContext, mSlotId,
+ mSubId, mLooper, mPresencePublication, mPresenceSubscriber,
+ mPublishStateCallbacks);
+ verify(mPresencePublication).handleAssociatedSubscriptionChanged(1);
+ verify(mPresenceSubscriber).handleAssociatedSubscriptionChanged(1);
+ waitForHandlerAction(uceImpl.getHandler(), 1000);
+ verify(mImsMmTelManager, atLeast(1)).registerImsRegistrationCallback(
+ any(Executor.class), any(RegistrationManager.RegistrationCallback.class));
+ verify(mContext).registerReceiver(any(), any());
+ return uceImpl;
+ }
+}