[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);
+        }
+    }
+}