Merge "Restore "Add Data command in Telephony""
diff --git a/Android.bp b/Android.bp
index a81158d..a7ae0cc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,6 +23,7 @@
"voip-common",
"ims-common",
"libprotobuf-java-lite",
+ "unsupportedappusage",
],
static_libs: [
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7313adb..a29fa5d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,8 +35,8 @@
<protected-broadcast android:name="android.intent.action.DATA_CONNECTION_FAILED" />
<protected-broadcast android:name="android.intent.action.DATA_STALL_DETECTED" />
<protected-broadcast android:name="android.intent.action.SIM_STATE_CHANGED" />
- <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIME" />
- <protected-broadcast android:name="com.android.internal.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS" />
+ <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" />
+ <protected-broadcast android:name="android.telephony.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS" />
<protected-broadcast android:name="android.intent.action.ACTION_MDN_STATE_CHANGED" />
<protected-broadcast android:name="android.provider.Telephony.SPN_STRINGS_UPDATED" />
<protected-broadcast android:name="android.provider.Telephony.SIM_FULL" />
@@ -161,6 +161,7 @@
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
<uses-permission android:name="android.permission.NETWORK_FACTORY" />
<uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
@@ -173,7 +174,7 @@
<!-- BIND_CARRIER_MESSAGING_SERVICE has been deprecated in favor of BIND_CARRIER_SERVICES. -->
<uses-permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE" />
<uses-permission android:name="android.permission.BIND_EUICC_SERVICE" />
- <uses-permission android:name="com.android.permission.BIND_TELEPHONY_NETWORK_SERVICE" />
+ <uses-permission android:name="android.permission.BIND_TELEPHONY_NETWORK_SERVICE" />
<uses-permission android:name="android.permission.BIND_CELL_BROADCAST_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
@@ -491,7 +492,7 @@
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="com.android.phone.action.ACTION_SHOW_ECM_EXIT_DIALOG" />
- <action android:name="com.android.internal.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS" />
+ <action android:name="android.telephony.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -651,5 +652,13 @@
<category android:name="android.intent.category.VOICE_LAUNCH" />
</intent-filter>
</activity>
+
+ <provider
+ android:name="ServiceStateProvider"
+ android:authorities="service-state"
+ android:exported="true"
+ android:multiprocess="false"
+ android:singleUser="true"
+ android:writePermission="android.permission.MODIFY_PHONE_STATE"/>
</application>
</manifest>
diff --git a/apex/Android.bp b/apex/Android.bp
index 86ebe3a..f9e4b67 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -1,19 +1,24 @@
-apex {
- name: "com.android.telephony",
-
- manifest: "apex_manifest.json",
+apex_defaults {
+ name: "com.android.telephony-defaults",
// optional. if unspecified, a default one is auto-generated
androidManifest: "AndroidManifest.xml",
java_libs: ["telephony-common", "ims-common"],
//apps: ["TeleService", "StkLib", "ONSLib"],
- apps: ["StkLib"],
key: "com.android.telephony.key",
certificate: ":com.android.telephony.certificate",
}
+apex {
+ name: "com.android.telephony",
+ manifest: "apex_manifest.json",
+ apps: ["StkLib"],
+
+ defaults:["com.android.telephony-defaults"],
+}
+
apex_key {
name: "com.android.telephony.key",
public_key: "com.android.telephony.avbpubkey",
diff --git a/apex/testing/Android.bp b/apex/testing/Android.bp
new file mode 100644
index 0000000..10455a4
--- /dev/null
+++ b/apex/testing/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 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.
+
+apex {
+ name: "test_com.android.telephony",
+ visibility: [
+ "//system/apex/tests",
+ ],
+ defaults: ["com.android.telephony-defaults"],
+ manifest: "test_manifest.json",
+ file_contexts: ":com.android.telephony-file_contexts",
+ // Test APEX, should never be installed
+ installable: false,
+}
\ No newline at end of file
diff --git a/apex/testing/AndroidManifest.xml b/apex/testing/AndroidManifest.xml
new file mode 100644
index 0000000..e2af9f5
--- /dev/null
+++ b/apex/testing/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.telephony">
+ <!-- APEX does not have classes.dex -->
+ <application android:hasCode="false" />
+ <uses-sdk
+ android:targetSdkVersion="30"
+ />
+</manifest>
\ No newline at end of file
diff --git a/apex/testing/test_manifest.json b/apex/testing/test_manifest.json
new file mode 100644
index 0000000..30bfda2
--- /dev/null
+++ b/apex/testing/test_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.telephony",
+ "version": 2147483647
+}
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 8bc1919..8c36b1a 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -235,6 +235,12 @@
<!-- Flag indicating whether the device supports RTT (real-time text) -->
<bool name="config_support_rtt">false</bool>
+ <!-- String indicating the package name of the device ImsService implementation for MMTEL. -->
+ <string name="config_ims_mmtel_package"></string>
+
+ <!-- String indicating the package name of the device ImsService implementation for RCS. -->
+ <string name="config_ims_rcs_package"></string>
+
<!-- The package name for the platform number verification supplier app. -->
<string name="platform_number_verification_package" translatable="false"></string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dfec1d8..46bbe2a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -438,6 +438,17 @@
<item>LTE/TDSCDMA/GSM/WCDMA</item>
<item>TDSCDMA/CDMA/EVDO/GSM/WCDMA </item>
<item>LTE/TDSCDMA/CDMA/EVDO/GSM/WCDMA</item>
+ <item>NR only</item>
+ <item>NR/LTE</item>
+ <item>NR/LTE/CDMA/EvDo</item>
+ <item>NR/LTE/GSM/WCDMA</item>
+ <item>NR/LTE/CDMA/EvDo/GSM/WCDMA</item>
+ <item>NR/LTE/WCDMA</item>
+ <item>NR/LTE/TDSCDMA</item>
+ <item>NR/LTE/TDSCDMA/GSM</item>
+ <item>NR/LTE/TDSCDMA/WCDMA</item>
+ <item>NR/LTE/TDSCDMA/GSM/WCDMA</item>
+ <item>NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA</item>
</string-array>
<!-- The preferred network modes RIL constants, in order of the modes above,
e.g. the choice "GSM/WCDMA preferred" has the corresponding value "0" -->
@@ -465,6 +476,17 @@
<item>"20"</item>
<item>"21"</item>
<item>"22"</item>
+ <item>"23"</item>
+ <item>"24"</item>
+ <item>"25"</item>
+ <item>"26"</item>
+ <item>"27"</item>
+ <item>"28"</item>
+ <item>"29"</item>
+ <item>"30"</item>
+ <item>"31"</item>
+ <item>"32"</item>
+ <item>"33"</item>
</string-array>
<!-- The following strings are summaries for preferred network modes in Mobile network settings,
@@ -493,6 +515,8 @@
<string name="preferred_network_mode_lte_gsm_wcdma_summary">Preferred network mode: GSM/WCDMA/LTE</string>
<!-- CDMA+LTE/EVDO -->
<string name="preferred_network_mode_lte_cdma_evdo_summary">Preferred network mode: CDMA+LTE/EVDO</string>
+ <!-- LTE/CDMA/EvDo/GSM/WCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_lte_cdma_evdo_gsm_wcdma_summary">Preferred network mode: LTE/CDMA/EvDo/GSM/WCDMA</string>
<!-- Global -->
<string name="preferred_network_mode_global_summary">Preferred network mode: Global</string>
<!-- LTE / WCDMA -->
@@ -521,6 +545,28 @@
<string name="preferred_network_mode_tdscdma_cdma_evdo_gsm_wcdma_summary">Preferred network mode: TDSCDMA/CDMA/EvDo/GSM/WCDMA</string>
<!-- LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA -->
<string name="preferred_network_mode_lte_tdscdma_cdma_evdo_gsm_wcdma_summary">Preferred network mode: LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA</string>
+ <!-- NR only [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_only_summary">Preferred network mode: NR only</string>
+ <!-- NR / LTE [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_summary">Preferred network mode: NR / LTE</string>
+ <!-- NR/LTE/CDMA/EvDo [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_cdma_evdo_summary">Preferred network mode: NR/LTE/CDMA/EvDo</string>
+ <!-- NR/LTE/GSM/WCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_gsm_wcdma_summary">Preferred network mode: NR/LTE/GSM/WCDMA</string>
+ <!-- NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_cdma_evdo_gsm_wcdma_summary">Preferred network mode: NR/LTE/CDMA/EvDo/GSM/WCDMA</string>
+ <!-- NR/LTE/WCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_wcdma_summary">Preferred network mode: NR/LTE/WCDMA</string>
+ <!-- NR/LTE/TDSCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_tdscdma_summary">Preferred network mode: NR/LTE/TDSCDMA</string>
+ <!-- NR/LTE/TDSCDMA/GSM [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_tdscdma_gsm_summary">Preferred network mode: NR/LTE/TDSCDMA/GSM</string>
+ <!-- NR/LTE/TDSCDMA/WCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_tdscdma_wcdma_summary">Preferred network mode: NR/LTE/TDSCDMA/WCDMA</string>
+ <!-- NR/LTE/TDSCDMA/GSM/WCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_tdscdma_gsm_wcdma_summary">Preferred network mode: NR/LTE/TDSCDMA/GSM/WCDMA</string>
+ <!-- NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA [CHAR LIMIT=NONE] -->
+ <string name="preferred_network_mode_nr_lte_tdscdma_cdma_evdo_gsm_wcdma_summary">Preferred network mode: NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA</string>
<!-- Mobile network settings screen, name for call settings category -->
<string name="call_category">Calling</string>
@@ -757,6 +803,10 @@
<string name="multi_category_enable">Multi-category enabled</string>
<string name="multi_category_disable">Multi-category disabled</string>
+ <string name="network_recommended">\u0020(recommended)</string>
+ <string name="network_5G" translatable="false">5G</string>
+ <string name="network_lte_pure" translatable="false">LTE</string>
+ <string name="network_4G_pure" translatable="false">4G</string>
<string name="network_lte">LTE (recommended)</string>
<string name="network_4G">4G (recommended)</string>
<string name="network_3G" translatable="false">3G</string>
diff --git a/src/com/android/phone/CLIRListPreference.java b/src/com/android/phone/CLIRListPreference.java
index 5c6132b..d8a9041 100755
--- a/src/com/android/phone/CLIRListPreference.java
+++ b/src/com/android/phone/CLIRListPreference.java
@@ -47,10 +47,15 @@
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
- mPhone.setOutgoingCallerIdDisplay(convertValueToCLIRMode(getValue()),
- mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLIR));
- if (mTcpListener != null) {
- mTcpListener.onStarted(this, false);
+ if (positiveResult && (getValue() != null)) {
+ mPhone.setOutgoingCallerIdDisplay(convertValueToCLIRMode(getValue()),
+ mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLIR));
+ if (mTcpListener != null) {
+ mTcpListener.onStarted(this, false);
+ }
+ } else {
+ Log.d(LOG_TAG, String.format("onDialogClosed: positiveResult=%b value=%s -- do nothing",
+ positiveResult, getValue()));
}
}
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 13dcaa7..d213fd9 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -1097,6 +1097,9 @@
@Override
public String getDefaultCarrierServicePackageName() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "getDefaultCarrierServicePackageName");
return mPlatformCarrierConfigPackage;
}
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index d1ff56f..06d2367 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -18,13 +18,22 @@
import android.content.Context;
import android.net.Uri;
+import android.os.Binder;
+import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.telephony.ims.ImsException;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.imsphone.ImsPhone;
+
import java.util.List;
/**
@@ -60,28 +69,94 @@
ServiceManager.addService(Context.TELEPHONY_IMS_SERVICE, this);
}
+ /**
+ * Register a capability callback which will provide RCS availability updates for the
+ * subscription specified.
+ *
+ * @param subId the subscription ID
+ * @param callback The ImsCapabilityCallback to be registered.
+ */
@Override
- public void registerRcsAvailabilityCallback(IImsCapabilityCallback c) {
+ public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
+ throws RemoteException {
enforceReadPrivilegedPermission("registerRcsAvailabilityCallback");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ getRcsFeatureManager(subId).registerRcsAvailabilityCallback(callback);
+ } catch (com.android.ims.ImsException e) {
+ Log.e(TAG, "registerRcsAvailabilityCallback: sudId=" + subId + ", " + e.getMessage());
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ /**
+ * Remove the registered capability callback.
+ *
+ * @param subId the subscription ID
+ * @param callback The ImsCapabilityCallback to be removed.
+ */
@Override
- public void unregisterRcsAvailabilityCallback(IImsCapabilityCallback c) {
+ public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
enforceReadPrivilegedPermission("unregisterRcsAvailabilityCallback");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ getRcsFeatureManager(subId).unregisterRcsAvailabilityCallback(callback);
+ } catch (com.android.ims.ImsException e) {
+ Log.e(TAG, "unregisterRcsAvailabilityCallback: sudId=" + subId + "," + e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ /**
+ * Query for the capability of an IMS RCS service
+ *
+ * @param subId the subscription ID
+ * @param capability the RCS capability to query.
+ * @param radioTech the radio tech that this capability failed for
+ * @return true if the RCS capability is capable for this subscription, false otherwise.
+ */
@Override
public boolean isCapable(int subId,
- @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
+ @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
enforceReadPrivilegedPermission("isCapable");
- return false;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getRcsFeatureManager(subId).isCapable(capability, radioTech);
+ } catch (com.android.ims.ImsException e) {
+ Log.e(TAG, "isCapable: sudId=" + subId
+ + ", capability=" + capability + ", " + e.getMessage());
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ /**
+ * Query the availability of an IMS RCS capability.
+ *
+ * @param subId the subscription ID
+ * @param capability the RCS capability to query.
+ * @return true if the RCS capability is currently available for the associated subscription,
+ * false otherwise.
+ */
@Override
public boolean isAvailable(int subId,
@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
enforceReadPrivilegedPermission("isAvailable");
- return false;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getRcsFeatureManager(subId).isAvailable(capability);
+ } catch (com.android.ims.ImsException e) {
+ Log.e(TAG, "isAvailable: sudId=" + subId
+ + ", capability=" + capability + ", " + e.getMessage());
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -125,4 +200,30 @@
private void enforceModifyPermission() {
mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
}
+
+ /**
+ * Retrieve RcsFeatureManager instance.
+ *
+ * @param subId the subscription ID
+ * @return The RcsFeatureManager instance
+ * @throws SecurityException if getting Phone or RcsFeatureManager instance failed.
+ */
+ private RcsFeatureManager getRcsFeatureManager(int subId) {
+ Phone phone = PhoneGlobals.getPhone(subId);
+ if (phone == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
+ "Invalid subscription Id: " + subId);
+ }
+ ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+ if (imsPhone == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "Cannot find ImsPhone instance: " + subId);
+ }
+ RcsFeatureManager rcsFeatureManager = imsPhone.getRcsManager();
+ if (rcsFeatureManager == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+ "Cannot find RcsFeatureManager instance: " + subId);
+ }
+ return rcsFeatureManager;
+ }
}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 812fabc..b81e77f 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -26,6 +26,7 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.media.AudioManager;
import android.net.ConnectivityManager;
@@ -66,6 +67,7 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.dataconnection.DataConnectionReasons;
import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
+import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.util.IndentingPrintWriter;
import com.android.phone.settings.SettingsConstants;
import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
@@ -146,6 +148,7 @@
CallNotifier notifier;
CallerInfoCache callerInfoCache;
NotificationMgr notificationMgr;
+ ImsResolver mImsResolver;
public PhoneInterfaceManager phoneMgr;
public ImsRcsController imsRcsController;
CarrierConfigLoader configLoader;
@@ -317,6 +320,19 @@
// Initialize the telephony framework
PhoneFactory.makeDefaultPhones(this);
+ // Only bring up ImsResolver if the device supports having an IMS stack.
+ if (getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_IMS)) {
+ // Get the package name of the default IMS implementation.
+ String defaultImsMmtelPackage = getResources().getString(
+ R.string.config_ims_mmtel_package);
+ String defaultImsRcsPackage = getResources().getString(
+ R.string.config_ims_rcs_package);
+ mImsResolver = new ImsResolver(this, defaultImsMmtelPackage,
+ defaultImsRcsPackage, PhoneFactory.getPhones().length);
+ mImsResolver.initialize();
+ }
+
// Start TelephonyDebugService After the default phone is created.
Intent intent = new Intent(this, TelephonyDebugService.class);
startService(intent);
@@ -438,6 +454,10 @@
return PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
}
+ public ImsResolver getImsResolver() {
+ return mImsResolver;
+ }
+
/* package */ CallManager getCallManager() {
return mCM;
}
@@ -877,6 +897,14 @@
pw.increaseIndent();
mDataRoamingNotifLog.dump(fd, pw, args);
pw.decreaseIndent();
+ pw.println("ImsResolver:");
+ pw.increaseIndent();
+ try {
+ if (mImsResolver != null) mImsResolver.dump(fd, pw, args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ pw.decreaseIndent();
pw.decreaseIndent();
pw.println("------- End PhoneGlobals -------");
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 07b0e4a..2391193 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -67,10 +67,11 @@
import android.telephony.CarrierConfigManager;
import android.telephony.CarrierRestrictionRules;
import android.telephony.CellIdentity;
+import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
import android.telephony.CellInfo;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoWcdma;
-import android.telephony.CellLocation;
import android.telephony.ClientRequestStats;
import android.telephony.ICellInfoCallback;
import android.telephony.IccOpenLogicalChannelResponse;
@@ -94,10 +95,8 @@
import android.telephony.UiccSlotInfo;
import android.telephony.UssdResponse;
import android.telephony.VisualVoicemailSmsFilterSettings;
-import android.telephony.cdma.CdmaCellLocation;
import android.telephony.data.ApnSetting;
import android.telephony.emergency.EmergencyNumber;
-import android.telephony.gsm.GsmCellLocation;
import android.telephony.ims.ImsException;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.RegistrationManager;
@@ -277,6 +276,7 @@
private PhoneGlobals mApp;
private CallManager mCM;
+ private ImsResolver mImsResolver;
private UserManager mUserManager;
private AppOpsManager mAppOps;
private MainThreadHandler mMainThreadHandler;
@@ -1101,7 +1101,7 @@
request = (MainThreadRequest) msg.obj;
WorkSource ws = (WorkSource) request.argument;
Phone phone = getPhoneFromRequest(request);
- phone.getCellLocation(ws, obtainMessage(EVENT_GET_CELL_LOCATION_DONE, request));
+ phone.getCellIdentity(ws, obtainMessage(EVENT_GET_CELL_LOCATION_DONE, request));
break;
case EVENT_GET_CELL_LOCATION_DONE:
ar = (AsyncResult) msg.obj;
@@ -1111,7 +1111,7 @@
} else {
phone = getPhoneFromRequest(request);
request.result = (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)
- ? new CdmaCellLocation() : new GsmCellLocation();
+ ? new CellIdentityCdma() : new CellIdentityGsm();
}
synchronized (request) {
@@ -1389,6 +1389,7 @@
private PhoneInterfaceManager(PhoneGlobals app) {
mApp = app;
mCM = PhoneGlobals.getInstance().mCM;
+ mImsResolver = PhoneGlobals.getInstance().getImsResolver();
mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
mMainThreadHandler = new MainThreadHandler();
@@ -2009,7 +2010,7 @@
}
@Override
- public Bundle getCellLocation(String callingPackage, String callingFeatureId) {
+ public CellIdentity getCellLocation(String callingPackage, String callingFeatureId) {
mApp.getSystemService(AppOpsManager.class)
.checkPackage(Binder.getCallingUid(), callingPackage);
@@ -2027,18 +2028,16 @@
case DENIED_HARD:
throw new SecurityException("Not allowed to access cell location");
case DENIED_SOFT:
- return new Bundle();
+ return (getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)
+ ? new CellIdentityCdma() : new CellIdentityGsm();
}
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
try {
if (DBG_LOC) log("getCellLocation: is active user");
- Bundle data = new Bundle();
int subId = mSubscriptionController.getDefaultDataSubId();
- CellLocation cl = (CellLocation) sendRequest(CMD_GET_CELL_LOCATION, workSource, subId);
- cl.fillInNotifierBundle(data);
- return data;
+ return (CellIdentity) sendRequest(CMD_GET_CELL_LOCATION, workSource, subId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -4509,12 +4508,11 @@
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return;
}
- resolver.enableIms(slotId);
+ mImsResolver.enableIms(slotId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -4529,12 +4527,11 @@
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return;
}
- resolver.disableIms(slotId);
+ mImsResolver.disableIms(slotId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -4551,12 +4548,11 @@
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return null;
}
- return resolver.getMmTelFeatureAndListen(slotId, callback);
+ return mImsResolver.getMmTelFeatureAndListen(slotId, callback);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -4573,12 +4569,11 @@
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return null;
}
- return resolver.getRcsFeatureAndListen(slotId, callback);
+ return mImsResolver.getRcsFeatureAndListen(slotId, callback);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -4593,12 +4588,11 @@
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return null;
}
- return resolver.getImsRegistration(slotId, feature);
+ return mImsResolver.getImsRegistration(slotId, feature);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -4613,12 +4607,11 @@
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return null;
}
- return resolver.getImsConfig(slotId, feature);
+ return mImsResolver.getImsConfig(slotId, feature);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -4627,58 +4620,65 @@
/**
* Sets the ImsService Package Name that Telephony will bind to.
*
- * @param slotId the slot ID that the ImsService should bind for.
- * @param isCarrierImsService true if the ImsService is the carrier override, false if the
+ * @param slotIndex the slot ID that the ImsService should bind for.
+ * @param isCarrierService true if the ImsService is the carrier override, false if the
* ImsService is the device default ImsService.
- * @param packageName The package name of the application that contains the ImsService to bind
- * to.
+ * @param featureTypes An integer array of feature types associated with a packageName.
+ * @param packageName The name of the package that the current configuration will be replaced
+ * with.
* @return true if setting the ImsService to bind to succeeded, false if it did not.
- * @hide
*/
- public boolean setImsService(int slotId, boolean isCarrierImsService, String packageName) {
- int[] subIds = SubscriptionManager.getSubId(slotId);
+ public boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+ int[] featureTypes, String packageName) {
+ int[] subIds = SubscriptionManager.getSubId(slotIndex);
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "setBoundImsServiceOverride");
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
(subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
- "setImsService");
+ "setBoundImsServiceOverride");
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return false;
}
- return resolver.overrideImsServiceConfiguration(slotId, isCarrierImsService,
- packageName);
+ Map<Integer, String> featureConfig = new HashMap<>();
+ for (int featureType : featureTypes) {
+ featureConfig.put(featureType, packageName);
+ }
+ return mImsResolver.overrideImsServiceConfiguration(slotIndex, isCarrierService,
+ featureConfig);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Return the ImsService configuration.
+ * Return the package name of the currently bound ImsService.
*
* @param slotId The slot that the ImsService is associated with.
* @param isCarrierImsService true, if the ImsService is a carrier override, false if it is
* the device default.
+ * @param featureType The feature associated with the queried configuration.
* @return the package name of the ImsService configuration.
*/
- public String getImsService(int slotId, boolean isCarrierImsService) {
+ public String getBoundImsServicePackage(int slotId, boolean isCarrierImsService,
+ @ImsFeature.FeatureType int featureType) {
int[] subIds = SubscriptionManager.getSubId(slotId);
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
- (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
- "getImsService");
+ TelephonyPermissions
+ .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp, (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
+ "getBoundImsServicePackage");
final long identity = Binder.clearCallingIdentity();
try {
- ImsResolver resolver = PhoneFactory.getImsResolver();
- if (resolver == null) {
+ if (mImsResolver == null) {
// may happen if the device does not support IMS.
return "";
}
// TODO: change API to query RCS separately.
- return resolver.getImsServiceConfiguration(slotId, isCarrierImsService,
- ImsFeature.FEATURE_MMTEL);
+ return mImsResolver.getImsServiceConfiguration(slotId, isCarrierImsService,
+ featureType);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/src/com/android/phone/ServiceStateProvider.java b/src/com/android/phone/ServiceStateProvider.java
new file mode 100644
index 0000000..69ea27c
--- /dev/null
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2019 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 static android.provider.Telephony.ServiceStateTable;
+import static android.provider.Telephony.ServiceStateTable.CDMA_DEFAULT_ROAMING_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_INDEX;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_MODE;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ROAMING_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
+import static android.provider.Telephony.ServiceStateTable.CSS_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_LONG;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_SHORT;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_NUMERIC;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DATA_ROAMING_TYPE;
+import static android.provider.Telephony.ServiceStateTable.IS_EMERGENCY_ONLY;
+import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
+import static android.provider.Telephony.ServiceStateTable.IS_USING_CARRIER_AGGREGATION;
+import static android.provider.Telephony.ServiceStateTable.NETWORK_ID;
+import static android.provider.Telephony.ServiceStateTable.OPERATOR_ALPHA_LONG_RAW;
+import static android.provider.Telephony.ServiceStateTable.OPERATOR_ALPHA_SHORT_RAW;
+import static android.provider.Telephony.ServiceStateTable.RIL_DATA_RADIO_TECHNOLOGY;
+import static android.provider.Telephony.ServiceStateTable.RIL_VOICE_RADIO_TECHNOLOGY;
+import static android.provider.Telephony.ServiceStateTable.SERVICE_STATE;
+import static android.provider.Telephony.ServiceStateTable.SYSTEM_ID;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_LONG;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_SHORT;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_NUMERIC;
+import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.VOICE_ROAMING_TYPE;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.net.Uri;
+import android.os.Parcel;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+
+/**
+ * The class to provide base facility to access ServiceState related content,
+ * which is stored in a SQLite database.
+ */
+public class ServiceStateProvider extends ContentProvider {
+ private static final String TAG = "ServiceStateProvider";
+
+ public static final String AUTHORITY = ServiceStateTable.AUTHORITY;
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ private final HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
+ private static final String[] sColumns = {
+ VOICE_REG_STATE,
+ DATA_REG_STATE,
+ VOICE_ROAMING_TYPE,
+ DATA_ROAMING_TYPE,
+ VOICE_OPERATOR_ALPHA_LONG,
+ VOICE_OPERATOR_ALPHA_SHORT,
+ VOICE_OPERATOR_NUMERIC,
+ DATA_OPERATOR_ALPHA_LONG,
+ DATA_OPERATOR_ALPHA_SHORT,
+ DATA_OPERATOR_NUMERIC,
+ IS_MANUAL_NETWORK_SELECTION,
+ RIL_VOICE_RADIO_TECHNOLOGY,
+ RIL_DATA_RADIO_TECHNOLOGY,
+ CSS_INDICATOR,
+ NETWORK_ID,
+ SYSTEM_ID,
+ CDMA_ROAMING_INDICATOR,
+ CDMA_DEFAULT_ROAMING_INDICATOR,
+ CDMA_ERI_ICON_INDEX,
+ CDMA_ERI_ICON_MODE,
+ IS_EMERGENCY_ONLY,
+ IS_USING_CARRIER_AGGREGATION,
+ OPERATOR_ALPHA_LONG_RAW,
+ OPERATOR_ALPHA_SHORT_RAW,
+ };
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ /**
+ * Returns the {@link ServiceState} information on specified subscription.
+ *
+ * @param subId whose subscriber id is returned
+ * @return the {@link ServiceState} information on specified subscription.
+ */
+ @VisibleForTesting
+ public ServiceState getServiceState(int subId) {
+ return mServiceStates.get(subId);
+ }
+
+ /**
+ * Returns the system's default subscription id.
+ *
+ * @return the "system" default subscription id.
+ */
+ @VisibleForTesting
+ public int getDefaultSubId() {
+ return SubscriptionManager.getDefaultSubscriptionId();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ if (uri.isPathPrefixMatch(CONTENT_URI)) {
+ // Parse the subId
+ int subId = 0;
+ try {
+ subId = Integer.parseInt(uri.getLastPathSegment());
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "insert: no subId provided in uri");
+ throw e;
+ }
+ Log.d(TAG, "subId=" + subId);
+
+ // handle DEFAULT_SUBSCRIPTION_ID
+ if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ subId = getDefaultSubId();
+ }
+
+ final Parcel p = Parcel.obtain();
+ final byte[] rawBytes = values.getAsByteArray(SERVICE_STATE);
+ p.unmarshall(rawBytes, 0, rawBytes.length);
+ p.setDataPosition(0);
+
+ // create the new service state
+ final ServiceState newSS = ServiceState.CREATOR.createFromParcel(p);
+
+ // notify listeners
+ // if ss is null (e.g. first service state update) we will notify for all fields
+ ServiceState ss = getServiceState(subId);
+ notifyChangeForSubIdAndField(getContext(), ss, newSS, subId);
+ notifyChangeForSubId(getContext(), ss, newSS, subId);
+
+ // store the new service state
+ mServiceStates.put(subId, newSS);
+ return uri;
+ }
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new RuntimeException("Not supported");
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new RuntimeException("Not supported");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new RuntimeException("Not supported");
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ if (!uri.isPathPrefixMatch(CONTENT_URI)) {
+ throw new IllegalArgumentException("Invalid URI: " + uri);
+ } else {
+ // Parse the subId
+ int subId = 0;
+ try {
+ subId = Integer.parseInt(uri.getLastPathSegment());
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "query: no subId provided in uri, using default.");
+ subId = getDefaultSubId();
+ }
+ Log.d(TAG, "subId=" + subId);
+
+ // handle DEFAULT_SUBSCRIPTION_ID
+ if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ subId = getDefaultSubId();
+ }
+
+ // Get the service state
+ ServiceState ss = getServiceState(subId);
+ if (ss == null) {
+ Log.d(TAG, "returning null");
+ return null;
+ }
+
+ // Build the result
+ final int voice_reg_state = ss.getVoiceRegState();
+ final int data_reg_state = ss.getDataRegState();
+ final int voice_roaming_type = ss.getVoiceRoamingType();
+ final int data_roaming_type = ss.getDataRoamingType();
+ final String voice_operator_alpha_long = ss.getOperatorAlphaLong();
+ final String voice_operator_alpha_short = ss.getOperatorAlphaShort();
+ final String voice_operator_numeric = ss.getOperatorNumeric();
+ final String data_operator_alpha_long = ss.getOperatorAlphaLong();
+ final String data_operator_alpha_short = ss.getOperatorAlphaShort();
+ final String data_operator_numeric = ss.getOperatorNumeric();
+ final int is_manual_network_selection = (ss.getIsManualSelection()) ? 1 : 0;
+ final int ril_voice_radio_technology = ss.getRilVoiceRadioTechnology();
+ final int ril_data_radio_technology = ss.getRilDataRadioTechnology();
+ final int css_indicator = ss.getCssIndicator();
+ final int network_id = ss.getCdmaNetworkId();
+ final int system_id = ss.getCdmaSystemId();
+ final int cdma_roaming_indicator = ss.getCdmaRoamingIndicator();
+ final int cdma_default_roaming_indicator = ss.getCdmaDefaultRoamingIndicator();
+ final int cdma_eri_icon_index = ss.getCdmaEriIconIndex();
+ final int cdma_eri_icon_mode = ss.getCdmaEriIconMode();
+ final int is_emergency_only = (ss.isEmergencyOnly()) ? 1 : 0;
+ final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
+ final String operator_alpha_long_raw = ss.getOperatorAlphaLongRaw();
+ final String operator_alpha_short_raw = ss.getOperatorAlphaShortRaw();
+
+ return buildSingleRowResult(projection, sColumns, new Object[] {
+ voice_reg_state,
+ data_reg_state,
+ voice_roaming_type,
+ data_roaming_type,
+ voice_operator_alpha_long,
+ voice_operator_alpha_short,
+ voice_operator_numeric,
+ data_operator_alpha_long,
+ data_operator_alpha_short,
+ data_operator_numeric,
+ is_manual_network_selection,
+ ril_voice_radio_technology,
+ ril_data_radio_technology,
+ css_indicator,
+ network_id,
+ system_id,
+ cdma_roaming_indicator,
+ cdma_default_roaming_indicator,
+ cdma_eri_icon_index,
+ cdma_eri_icon_mode,
+ is_emergency_only,
+ is_using_carrier_aggregation,
+ operator_alpha_long_raw,
+ operator_alpha_short_raw,
+ });
+ }
+ }
+
+ private static Cursor buildSingleRowResult(String[] projection, String[] availableColumns,
+ Object[] data) {
+ if (projection == null) {
+ projection = availableColumns;
+ }
+ final MatrixCursor c = new MatrixCursor(projection, 1);
+ final RowBuilder row = c.newRow();
+ for (int i = 0; i < c.getColumnCount(); i++) {
+ final String columnName = c.getColumnName(i);
+ boolean found = false;
+ for (int j = 0; j < availableColumns.length; j++) {
+ if (availableColumns[j].equals(columnName)) {
+ row.add(data[j]);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new IllegalArgumentException("Invalid column " + projection[i]);
+ }
+ }
+ return c;
+ }
+
+ /**
+ * Notify interested apps that certain fields of the ServiceState have changed.
+ *
+ * Apps which want to wake when specific fields change can use
+ * JobScheduler's TriggerContentUri. This replaces the waking functionality of the implicit
+ * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targeting version O.
+ *
+ * We will only notify for certain fields. This is an intentional change from the behavior of
+ * the broadcast. Listeners will be notified when the voice or data registration state or
+ * roaming type changes.
+ */
+ @VisibleForTesting
+ public static void notifyChangeForSubIdAndField(Context context, ServiceState oldSS,
+ ServiceState newSS, int subId) {
+ final boolean firstUpdate = (oldSS == null) ? true : false;
+
+ // for every field, if the field has changed values, notify via the provider
+ if (firstUpdate || voiceRegStateChanged(oldSS, newSS)) {
+ context.getContentResolver().notifyChange(
+ getUriForSubscriptionIdAndField(subId, VOICE_REG_STATE),
+ /* observer= */ null, /* syncToNetwork= */ false);
+ }
+ if (firstUpdate || dataRegStateChanged(oldSS, newSS)) {
+ context.getContentResolver().notifyChange(
+ getUriForSubscriptionIdAndField(subId, DATA_REG_STATE), null, false);
+ }
+ if (firstUpdate || voiceRoamingTypeChanged(oldSS, newSS)) {
+ context.getContentResolver().notifyChange(
+ getUriForSubscriptionIdAndField(subId, VOICE_ROAMING_TYPE), null, false);
+ }
+ if (firstUpdate || dataRoamingTypeChanged(oldSS, newSS)) {
+ context.getContentResolver().notifyChange(
+ getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false);
+ }
+ }
+
+ private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) {
+ return oldSS.getVoiceRegState() != newSS.getVoiceRegState();
+ }
+
+ private static boolean dataRegStateChanged(ServiceState oldSS, ServiceState newSS) {
+ return oldSS.getDataRegState() != newSS.getDataRegState();
+ }
+
+ private static boolean voiceRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
+ return oldSS.getVoiceRoamingType() != newSS.getVoiceRoamingType();
+ }
+
+ private static boolean dataRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
+ return oldSS.getDataRoamingType() != newSS.getDataRoamingType();
+ }
+
+ /**
+ * Notify interested apps that the ServiceState has changed.
+ *
+ * Apps which want to wake when any field in the ServiceState has changed can use
+ * JobScheduler's TriggerContentUri. This replaces the waking functionality of the implicit
+ * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targeting version O.
+ *
+ * We will only notify for certain fields. This is an intentional change from the behavior of
+ * the broadcast. Listeners will only be notified when the voice/data registration state or
+ * roaming type changes.
+ */
+ @VisibleForTesting
+ public static void notifyChangeForSubId(Context context, ServiceState oldSS, ServiceState newSS,
+ int subId) {
+ // if the voice or data registration or roaming state field has changed values, notify via
+ // the provider.
+ // If oldSS is null and newSS is not (e.g. first update of service state) this will also
+ // notify
+ if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS)
+ || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)) {
+ context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
+ }
+ }
+}
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 0806fd1..53b3356 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -26,6 +26,7 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
@@ -35,6 +36,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.TreeSet;
@@ -180,13 +182,15 @@
private void onHelpIms() {
PrintWriter pw = getOutPrintWriter();
pw.println("IMS Commands:");
- pw.println(" ims set-ims-service [-s SLOT_ID] (-c | -d) PACKAGE_NAME");
+ pw.println(" ims set-ims-service [-s SLOT_ID] (-c | -d | -f) PACKAGE_NAME");
pw.println(" Sets the ImsService defined in PACKAGE_NAME to to be the bound");
pw.println(" ImsService. Options are:");
pw.println(" -s: the slot ID that the ImsService should be bound for. If no option");
pw.println(" is specified, it will choose the default voice SIM slot.");
pw.println(" -c: Override the ImsService defined in the carrier configuration.");
pw.println(" -d: Override the ImsService defined in the device overlay.");
+ pw.println(" -f: Set the feature that this override if for, if no option is");
+ pw.println(" specified, the new package name will be used for all features.");
pw.println(" ims get-ims-service [-s SLOT_ID] [-c | -d]");
pw.println(" Gets the package name of the currently defined ImsService.");
pw.println(" Options are:");
@@ -194,6 +198,8 @@
pw.println(" is specified, it will choose the default voice SIM slot.");
pw.println(" -c: The ImsService defined as the carrier configured ImsService.");
pw.println(" -c: The ImsService defined as the device default ImsService.");
+ pw.println(" -f: The feature type that the query will be requested for. If none is");
+ pw.println(" specified, the returned package name will correspond to MMTEL.");
pw.println(" ims enable [-s SLOT_ID]");
pw.println(" enables IMS for the SIM slot specified, or for the default voice SIM slot");
pw.println(" if none is specified.");
@@ -444,6 +450,7 @@
PrintWriter errPw = getErrPrintWriter();
int slotId = getDefaultSlot();
Boolean isCarrierService = null;
+ List<Integer> featuresList = new ArrayList<>();
String opt;
while ((opt = getNextOption()) != null) {
@@ -465,6 +472,26 @@
isCarrierService = false;
break;
}
+ case "-f": {
+ String featureString = getNextArgRequired();
+ String[] features = featureString.split(",");
+ for (int i = 0; i < features.length; i++) {
+ try {
+ Integer result = Integer.parseInt(features[i]);
+ if (result < ImsFeature.FEATURE_EMERGENCY_MMTEL
+ || result >= ImsFeature.FEATURE_MAX) {
+ errPw.println("ims set-ims-service -f " + result
+ + " is an invalid feature.");
+ return -1;
+ }
+ featuresList.add(result);
+ } catch (NumberFormatException e) {
+ errPw.println("ims set-ims-service -f tried to parse " + features[i]
+ + " as an integer.");
+ return -1;
+ }
+ }
+ }
}
}
// Mandatory param, either -c or -d
@@ -479,16 +506,24 @@
if (packageName == null) {
packageName = "";
}
- boolean result = mInterface.setImsService(slotId, isCarrierService, packageName);
+ int[] featureArray = new int[featuresList.size()];
+ for (int i = 0; i < featuresList.size(); i++) {
+ featureArray[i] = featuresList.get(i);
+ }
+ boolean result = mInterface.setBoundImsServiceOverride(slotId, isCarrierService,
+ featureArray, packageName);
if (VDBG) {
Log.v(LOG_TAG, "ims set-ims-service -s " + slotId + " "
- + (isCarrierService ? "-c " : "-d ") + packageName + ", result=" + result);
+ + (isCarrierService ? "-c " : "-d ")
+ + "-f " + featuresList + " "
+ + packageName + ", result=" + result);
}
getOutPrintWriter().println(result);
} catch (RemoteException e) {
Log.w(LOG_TAG, "ims set-ims-service -s " + slotId + " "
- + (isCarrierService ? "-c " : "-d ") + packageName + ", error"
- + e.getMessage());
+ + (isCarrierService ? "-c " : "-d ")
+ + "-f " + featuresList + " "
+ + packageName + ", error" + e.getMessage());
errPw.println("Exception: " + e.getMessage());
return -1;
}
@@ -500,6 +535,7 @@
PrintWriter errPw = getErrPrintWriter();
int slotId = getDefaultSlot();
Boolean isCarrierService = null;
+ Integer featureType = ImsFeature.FEATURE_MMTEL;
String opt;
while ((opt = getNextOption()) != null) {
@@ -521,23 +557,38 @@
isCarrierService = false;
break;
}
+ case "-f": {
+ try {
+ featureType = Integer.parseInt(getNextArg());
+ } catch (NumberFormatException e) {
+ errPw.println("ims get-ims-service -f requires valid integer as feature.");
+ return -1;
+ }
+ if (featureType < ImsFeature.FEATURE_EMERGENCY_MMTEL
+ || featureType >= ImsFeature.FEATURE_MAX) {
+ errPw.println("ims get-ims-service -f invalid feature.");
+ return -1;
+ }
+ }
}
}
// Mandatory param, either -c or -d
if (isCarrierService == null) {
- errPw.println("ims set-ims-service requires either \"-c\" or \"-d\" to be set.");
+ errPw.println("ims get-ims-service requires either \"-c\" or \"-d\" to be set.");
return -1;
}
String result;
try {
- result = mInterface.getImsService(slotId, isCarrierService);
+ result = mInterface.getBoundImsServicePackage(slotId, isCarrierService, featureType);
} catch (RemoteException e) {
return -1;
}
if (VDBG) {
Log.v(LOG_TAG, "ims get-ims-service -s " + slotId + " "
- + (isCarrierService ? "-c " : "-d ") + ", returned: " + result);
+ + (isCarrierService ? "-c " : "-d ")
+ + (featureType != null ? ("-f " + featureType) : "") + " , returned: "
+ + result);
}
getOutPrintWriter().println(result);
return 0;
diff --git a/src/com/android/phone/otasp/OtaspSimStateReceiver.java b/src/com/android/phone/otasp/OtaspSimStateReceiver.java
index 78f7baf..bb4022a 100644
--- a/src/com/android/phone/otasp/OtaspSimStateReceiver.java
+++ b/src/com/android/phone/otasp/OtaspSimStateReceiver.java
@@ -18,9 +18,11 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -32,15 +34,32 @@
private static final boolean DBG = true;
private Context mContext;
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener(){
+ private static final int EVENT_OTASP_CHANGED = 1;
+
+ private Handler mOtaspHandler = new Handler() {
@Override
- public void onOtaspChanged(int otaspMode) {
- logd("onOtaspChanged: otaspMode=" + otaspMode);
- if (otaspMode == TelephonyManager.OTASP_NEEDED) {
- logd("otasp activation required, start otaspActivationService");
- mContext.startService(new Intent(mContext, OtaspActivationService.class));
- } else if (otaspMode == TelephonyManager.OTASP_NOT_NEEDED) {
- OtaspActivationService.updateActivationState(mContext, true);
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+ switch (msg.what) {
+ case EVENT_OTASP_CHANGED:
+ ar = (AsyncResult) msg.obj;
+ if (ar.exception == null && ar.result != null) {
+ int otaspMode = (Integer) ar.result;
+ logd("EVENT_OTASP_CHANGED: otaspMode=" + otaspMode);
+ if (otaspMode == TelephonyManager.OTASP_NEEDED) {
+ logd("otasp activation required, start otaspActivationService");
+ mContext.startService(
+ new Intent(mContext, OtaspActivationService.class));
+ } else if (otaspMode == TelephonyManager.OTASP_NOT_NEEDED) {
+ OtaspActivationService.updateActivationState(mContext, true);
+ }
+ } else {
+ logd("EVENT_OTASP_CHANGED: exception=" + ar.exception);
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
}
}
};
@@ -74,13 +93,17 @@
if(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
if (DBG) logd("Received intent: " + intent.getAction());
if (PhoneGlobals.getPhone().getIccRecordsLoaded() && isCarrierSupported()) {
- final TelephonyManager telephonyManager = TelephonyManager.from(context);
- telephonyManager.listen(mPhoneStateListener,
- PhoneStateListener.LISTEN_OTASP_CHANGED);
+ registerOtaspChangedHandler();
}
}
}
+ // It's fine to call mutiple times, as the registrants are de-duped by Handler object.
+ private void registerOtaspChangedHandler() {
+ final Phone phone = PhoneGlobals.getPhone();
+ phone.registerForOtaspChange(mOtaspHandler, EVENT_OTASP_CHANGED, null);
+ }
+
private static void logd(String s) {
Log.d(TAG, s);
}
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index ce5b839..77f1135 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -111,28 +111,40 @@
private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
private static final String[] PREFERRED_NETWORK_LABELS = {
- "WCDMA preferred",
+ "GSM/WCDMA preferred",
"GSM only",
"WCDMA only",
- "GSM auto (PRL)",
- "CDMA auto (PRL)",
+ "GSM/WCDMA auto (PRL)",
+ "CDMA/EvDo auto (PRL)",
"CDMA only",
"EvDo only",
- "Global auto (PRL)",
- "LTE/CDMA auto (PRL)",
- "LTE/UMTS auto (PRL)",
- "LTE/CDMA/UMTS auto (PRL)",
+ "CDMA/EvDo/GSM/WCDMA (PRL)",
+ "CDMA + LTE/EvDo (PRL)",
+ "GSM/WCDMA/LTE (PRL)",
+ "LTE/CDMA/EvDo/GSM/WCDMA (PRL)",
"LTE only",
"LTE/WCDMA",
- "TD-SCDMA only",
- "TD-SCDMA/WCDMA",
- "LTE/TD-SCDMA",
- "TD-SCDMA/GSM",
- "TD-SCDMA/UMTS",
- "LTE/TD-SCDMA/WCDMA",
- "LTE/TD-SCDMA/UMTS",
- "TD-SCDMA/CDMA/UMTS",
- "Global/TD-SCDMA",
+ "TDSCDMA only",
+ "TDSCDMA/WCDMA",
+ "LTE/TDSCDMA",
+ "TDSCDMA/GSM",
+ "LTE/TDSCDMA/GSM",
+ "TDSCDMA/GSM/WCDMA",
+ "LTE/TDSCDMA/WCDMA",
+ "LTE/TDSCDMA/GSM/WCDMA",
+ "TDSCDMA/CDMA/EvDo/GSM/WCDMA ",
+ "LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA",
+ "NR only",
+ "NR/LTE",
+ "NR/LTE/CDMA/EvDo",
+ "NR/LTE/GSM/WCDMA",
+ "NR/LTE/CDMA/EvDo/GSM/WCDMA",
+ "NR/LTE/WCDMA",
+ "NR/LTE/TDSCDMA",
+ "NR/LTE/TDSCDMA/GSM",
+ "NR/LTE/TDSCDMA/WCDMA",
+ "NR/LTE/TDSCDMA/GSM/WCDMA",
+ "NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA",
"Unknown"
};
diff --git a/src/com/android/phone/settings/fdn/EditFdnContactScreen.java b/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
index 0eda140..140cc74 100644
--- a/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
+++ b/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
@@ -443,7 +443,7 @@
}
if (v == mNameField) {
- mNumberField.requestFocus();
+ mButton.requestFocus();
} else if (v == mNumberField) {
mButton.requestFocus();
} else if (v == mButton) {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 5a9fbcf..fb865b4 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -203,6 +203,7 @@
// whether the call should have the HD audio property set.
refreshConferenceSupported();
refreshDisableAddCall();
+ refreshHoldSupported();
updateConnectionProperties();
break;
@@ -1345,6 +1346,18 @@
}
}
+ private void refreshHoldSupported() {
+ if (mOriginalConnection == null) {
+ Log.w(this, "refreshHoldSupported org conn is null");
+ return;
+ }
+
+ if (!mOriginalConnection.shouldAllowHoldingVideoCall() && canHoldImsCalls() !=
+ ((getConnectionCapabilities() & (CAPABILITY_HOLD | CAPABILITY_SUPPORT_HOLD)) != 0)) {
+ updateConnectionCapabilities();
+ }
+ }
+
private void refreshDisableAddCall() {
if (shouldSetDisableAddCallExtra()) {
Bundle newExtras = getExtras();
@@ -1501,8 +1514,10 @@
private boolean canHoldImsCalls() {
PersistableBundle b = getCarrierConfig();
// Return true if the CarrierConfig is unavailable
- return !doesDeviceRespectHoldCarrierConfig() || b == null ||
- b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL);
+ return (!doesDeviceRespectHoldCarrierConfig() || b == null ||
+ b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL)) &&
+ ((mOriginalConnection != null && mOriginalConnection.shouldAllowHoldingVideoCall())
+ || !VideoProfile.isVideo(getVideoState()));
}
@VisibleForTesting
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 40c90ec..176a4c7 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -1458,16 +1458,32 @@
phone.getEmergencyNumberTracker().getEmergencyNumber(number);
if (emergencyNumber != null) {
phone.notifyOutgoingEmergencyCall(emergencyNumber);
- // If we do not support holding ongoing calls for an outgoing emergency call,
- // disconnect the ongoing calls.
- if (!shouldHoldForEmergencyCall(phone) && !getAllConnections().isEmpty()) {
- for (Connection c : getAllConnections()) {
- if (!c.equals(connection)
- && c.getState() != Connection.STATE_DISCONNECTED
- && c instanceof TelephonyConnection) {
- ((TelephonyConnection) c).hangup(
- android.telephony.DisconnectCause
- .OUTGOING_EMERGENCY_CALL_PLACED);
+ if (!getAllConnections().isEmpty()) {
+ if (!shouldHoldForEmergencyCall(phone)) {
+ // If we do not support holding ongoing calls for an outgoing
+ // emergency call, disconnect the ongoing calls.
+ for (Connection c : getAllConnections()) {
+ if (!c.equals(connection)
+ && c.getState() != Connection.STATE_DISCONNECTED
+ && c instanceof TelephonyConnection) {
+ ((TelephonyConnection) c).hangup(
+ android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ }
+ }
+ } else if (!isVideoCallHoldAllowed(phone)) {
+ // If we do not support holding ongoing video call for an outgoing
+ // emergency call, disconnect the ongoing video call.
+ for (Connection c : getAllConnections()) {
+ if (!c.equals(connection)
+ && c.getState() == Connection.STATE_ACTIVE
+ && VideoProfile.isVideo(c.getVideoState())
+ && c instanceof TelephonyConnection) {
+ ((TelephonyConnection) c).hangup(
+ android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ break;
+ }
}
}
}
@@ -1537,6 +1553,18 @@
}
}
+ private boolean isVideoCallHoldAllowed(Phone phone) {
+ CarrierConfigManager cfgManager = (CarrierConfigManager)
+ phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (cfgManager == null) {
+ // For some reason CarrierConfigManager is unavailable, return default
+ Log.w(this, "isVideoCallHoldAllowed: couldn't get CarrierConfigManager");
+ return true;
+ }
+ return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
+ CarrierConfigManager.KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
+ }
+
private boolean shouldHoldForEmergencyCall(Phone phone) {
CarrierConfigManager cfgManager = (CarrierConfigManager)
phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
index 96f8bf7..f8d4487 100644
--- a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
+++ b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
@@ -50,7 +50,6 @@
put(PhoneStateListener.LISTEN_DATA_CONNECTION_STATE, "DATA_CONNECTION_STATE");
put(PhoneStateListener.LISTEN_DATA_ACTIVITY, "DATA_ACTIVITY");
put(PhoneStateListener.LISTEN_SIGNAL_STRENGTHS, "SIGNAL_STRENGTHS");
- put(PhoneStateListener.LISTEN_OTASP_CHANGED, "OTASP_CHANGED");
put(PhoneStateListener.LISTEN_CELL_INFO, "CELL_INFO");
put(PhoneStateListener.LISTEN_PRECISE_CALL_STATE, "PRECISE_CALL_STATE");
put(PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE,
diff --git a/tests/Android.bp b/tests/Android.bp
index 22b40b5..7ed234e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -25,6 +25,7 @@
"telephony-common",
"android.test.base",
"ims-common",
+ "android.test.mock",
],
platform_apis: true,
certificate: "platform",
diff --git a/tests/src/com/android/phone/ServiceStateProviderTest.java b/tests/src/com/android/phone/ServiceStateProviderTest.java
new file mode 100644
index 0000000..5f13389
--- /dev/null
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2019 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 static android.provider.Telephony.ServiceStateTable;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for simple queries of ServiceStateProvider.
+ *
+ * Build, install and run the tests by running the commands below:
+ * runtest --path <dir or file>
+ * runtest --path <dir or file> --test-method <testMethodName>
+ * e.g.)
+ * runtest --path tests/src/com/android/phone/ServiceStateProviderTest.java \
+ * --test-method testGetServiceState
+ */
+public class ServiceStateProviderTest {
+ private static final String TAG = "ServiceStateProviderTest";
+
+ private Context mContext;
+ private MockContentResolver mContentResolver;
+ private ServiceState mTestServiceState;
+ private ServiceState mTestServiceStateForSubId1;
+
+ private final String[] mTestProjection =
+ {
+ ServiceStateTable.VOICE_REG_STATE,
+ ServiceStateTable.DATA_REG_STATE,
+ ServiceStateTable.VOICE_OPERATOR_ALPHA_LONG,
+ ServiceStateTable.VOICE_OPERATOR_ALPHA_SHORT,
+ ServiceStateTable.VOICE_OPERATOR_NUMERIC,
+ ServiceStateTable.DATA_OPERATOR_ALPHA_LONG,
+ ServiceStateTable.DATA_OPERATOR_ALPHA_SHORT,
+ ServiceStateTable.DATA_OPERATOR_NUMERIC,
+ ServiceStateTable.IS_MANUAL_NETWORK_SELECTION,
+ ServiceStateTable.RIL_VOICE_RADIO_TECHNOLOGY,
+ ServiceStateTable.RIL_DATA_RADIO_TECHNOLOGY,
+ ServiceStateTable.CSS_INDICATOR,
+ ServiceStateTable.NETWORK_ID,
+ ServiceStateTable.SYSTEM_ID,
+ ServiceStateTable.CDMA_ROAMING_INDICATOR,
+ ServiceStateTable.CDMA_DEFAULT_ROAMING_INDICATOR,
+ ServiceStateTable.CDMA_ERI_ICON_INDEX,
+ ServiceStateTable.CDMA_ERI_ICON_MODE,
+ ServiceStateTable.IS_EMERGENCY_ONLY,
+ ServiceStateTable.IS_USING_CARRIER_AGGREGATION,
+ ServiceStateTable.OPERATOR_ALPHA_LONG_RAW,
+ ServiceStateTable.OPERATOR_ALPHA_SHORT_RAW,
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = mock(Context.class);
+ mContentResolver = new MockContentResolver() {
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ throw new RuntimeException("notifyChange!");
+ }
+ };
+ doReturn(mContentResolver).when(mContext).getContentResolver();
+
+ mTestServiceState = new ServiceState();
+ mTestServiceState.setStateOutOfService();
+ mTestServiceStateForSubId1 = new ServiceState();
+ mTestServiceStateForSubId1.setStateOff();
+
+ // Mock out the actual phone state
+ ServiceStateProvider provider = new ServiceStateProvider() {
+ @Override
+ public ServiceState getServiceState(int subId) {
+ if (subId == 1) {
+ return mTestServiceStateForSubId1;
+ } else {
+ return mTestServiceState;
+ }
+ }
+
+ @Override
+ public int getDefaultSubId() {
+ return 0;
+ }
+ };
+ ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.authority = "service-state";
+ provider.attachInfoForTesting(mContext, providerInfo);
+ mContentResolver.addProvider("service-state", provider);
+ }
+
+ @Test
+ @SmallTest
+ public void testQueryServiceStateWithNoSubId() {
+ // Verify that when calling query with no subId in the uri the default ServiceState is
+ // returned.
+ // In this case the subId is set to 0 and the expected service state is
+ // mTestServiceState.
+ verifyServiceStateForSubId(ServiceStateTable.CONTENT_URI, mTestServiceState);
+ }
+
+ @Test
+ @SmallTest
+ public void testGetServiceStateWithDefaultSubId() {
+ // Verify that when calling with the DEFAULT_SUBSCRIPTION_ID the correct ServiceState is
+ // returned
+ // In this case the subId is set to 0 and the expected service state is
+ // mTestServiceState.
+ verifyServiceStateForSubId(
+ getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+ mTestServiceState);
+ }
+
+ /**
+ * Test querying the service state for a given subId
+ */
+ @Test
+ @SmallTest
+ public void testGetServiceStateForSubId() {
+ // Verify that when calling with a specific subId the correct ServiceState is returned
+ // In this case the subId is set to 1 and the expected service state is
+ // mTestServiceStateForSubId1
+ verifyServiceStateForSubId(getUriForSubscriptionId(1), mTestServiceStateForSubId1);
+ }
+
+ private void verifyServiceStateForSubId(Uri uri, ServiceState ss) {
+ Cursor cursor = mContentResolver.query(uri, mTestProjection, "",
+ null, null);
+ assertNotNull(cursor);
+ cursor.moveToFirst();
+
+ final int voiceRegState = ss.getVoiceRegState();
+ final int dataRegState = ss.getDataRegState();
+ final String voiceOperatorAlphaLong = ss.getOperatorAlphaLong();
+ final String voiceOperatorAlphaShort = ss.getOperatorAlphaShort();
+ final String voiceOperatorNumeric = ss.getOperatorNumeric();
+ final String dataOperatorAlphaLong = ss.getOperatorAlphaLong();
+ final String dataOperatorAlphaShort = ss.getOperatorAlphaShort();
+ final String dataOperatorNumeric = ss.getOperatorNumeric();
+ final int isManualNetworkSelection = (ss.getIsManualSelection()) ? 1 : 0;
+ final int rilVoiceRadioTechnology = ss.getRilVoiceRadioTechnology();
+ final int rilDataRadioTechnology = ss.getRilDataRadioTechnology();
+ final int cssIndicator = ss.getCssIndicator();
+ final int networkId = ss.getCdmaNetworkId();
+ final int systemId = ss.getCdmaSystemId();
+ final int cdmaRoamingIndicator = ss.getCdmaRoamingIndicator();
+ final int cdmaDefaultRoamingIndicator = ss.getCdmaDefaultRoamingIndicator();
+ final int cdmaEriIconIndex = ss.getCdmaEriIconIndex();
+ final int cdmaEriIconMode = ss.getCdmaEriIconMode();
+ final int isEmergencyOnly = (ss.isEmergencyOnly()) ? 1 : 0;
+ final int isUsingCarrierAggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
+ final String operatorAlphaLongRaw = ss.getOperatorAlphaLongRaw();
+ final String operatorAlphaShortRaw = ss.getOperatorAlphaShortRaw();
+
+ assertEquals(voiceRegState, cursor.getInt(0));
+ assertEquals(dataRegState, cursor.getInt(1));
+ assertEquals(voiceOperatorAlphaLong, cursor.getString(2));
+ assertEquals(voiceOperatorAlphaShort, cursor.getString(3));
+ assertEquals(voiceOperatorNumeric, cursor.getString(4));
+ assertEquals(dataOperatorAlphaLong, cursor.getString(5));
+ assertEquals(dataOperatorAlphaShort, cursor.getString(6));
+ assertEquals(dataOperatorNumeric, cursor.getString(7));
+ assertEquals(isManualNetworkSelection, cursor.getInt(8));
+ assertEquals(rilVoiceRadioTechnology, cursor.getInt(9));
+ assertEquals(rilDataRadioTechnology, cursor.getInt(10));
+ assertEquals(cssIndicator, cursor.getInt(11));
+ assertEquals(networkId, cursor.getInt(12));
+ assertEquals(systemId, cursor.getInt(13));
+ assertEquals(cdmaRoamingIndicator, cursor.getInt(14));
+ assertEquals(cdmaDefaultRoamingIndicator, cursor.getInt(15));
+ assertEquals(cdmaEriIconIndex, cursor.getInt(16));
+ assertEquals(cdmaEriIconMode, cursor.getInt(17));
+ assertEquals(isEmergencyOnly, cursor.getInt(18));
+ assertEquals(isUsingCarrierAggregation, cursor.getInt(19));
+ assertEquals(operatorAlphaLongRaw, cursor.getString(20));
+ assertEquals(operatorAlphaShortRaw, cursor.getString(21));
+ }
+
+ /**
+ * Test that we don't notify for certain field changes. (e.g. we don't notify when the NetworkId
+ * or SystemId change) This is an intentional behavior change from the broadcast.
+ */
+ @Test
+ @SmallTest
+ public void testNoNotify() {
+ int subId = 0;
+
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setCdmaSystemAndNetworkId(1, 1);
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+ newSS.setCdmaSystemAndNetworkId(0, 0);
+
+ // Test that notifyChange is not called for these fields
+ boolean notifyChangeWasCalled = false;
+ try {
+ ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
+ } catch (RuntimeException e) {
+ final String message = e.getMessage();
+ if (message != null && message.equals("notifyChange!")) {
+ notifyChangeWasCalled = true;
+ }
+ }
+ assertFalse(notifyChangeWasCalled);
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyChanged() {
+ int subId = 0;
+
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
+ ServiceState copyOfOldSS = new ServiceState();
+ copyOfOldSS.setStateOutOfService();
+ copyOfOldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+ newSS.setVoiceRegState(ServiceState.STATE_POWER_OFF);
+
+ // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
+ boolean notifyChangeWasCalled = false;
+ try {
+ ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, copyOfOldSS, subId);
+ } catch (RuntimeException e) {
+ final String message = e.getMessage();
+ if (message != null && message.equals("notifyChange!")) {
+ notifyChangeWasCalled = true;
+ }
+ }
+ assertFalse(notifyChangeWasCalled);
+
+ // Test that notifyChange is not called with no change in notifyChangeForSubId
+ notifyChangeWasCalled = false;
+ try {
+ ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, copyOfOldSS, subId);
+ } catch (RuntimeException e) {
+ final String message = e.getMessage();
+ if (message != null && message.equals("notifyChange!")) {
+ notifyChangeWasCalled = true;
+ }
+ }
+ assertFalse(notifyChangeWasCalled);
+
+ // Test that notifyChange is called by notifyChangeForSubIdAndField when the voice_reg_state
+ // changes
+ notifyChangeWasCalled = false;
+ try {
+ ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
+ } catch (RuntimeException e) {
+ final String message = e.getMessage();
+ if (message != null && message.equals("notifyChange!")) {
+ notifyChangeWasCalled = true;
+ }
+ }
+ assertTrue(notifyChangeWasCalled);
+
+ // Test that notifyChange is called by notifyChangeForSubId when the voice_reg_state changes
+ notifyChangeWasCalled = false;
+ try {
+ ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, newSS, subId);
+ } catch (RuntimeException e) {
+ final String message = e.getMessage();
+ if (message != null && message.equals("notifyChange!")) {
+ notifyChangeWasCalled = true;
+ }
+ }
+ assertTrue(notifyChangeWasCalled);
+ }
+}