[Mainline] move ServiceStateProvider to TeleService
-add ServiceStateProvider.java from TelephonyProvider
-add ServiceStateProviderTest.java from TelephonyProvider
-modify client/AndroidManifest.xml
-modify tests/Android.bp
Bug: 144486008
Test: atest ServiceStateProviderTest (PASS)
Change-Id: Id51aee376bf519b0a431f5f5caaea99e23fb669b
Merged-In: Id51aee376bf519b0a431f5f5caaea99e23fb669b
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);
+ }
+ }
+}