1 /* //device/content/providers/telephony/TelephonyProvider.java 2 ** 3 ** Copyright 2016, The Android Open Source Project 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 18 package com.android.providers.telephony; 19 20 import android.content.ContentProvider; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.database.MatrixCursor; 25 import android.database.MatrixCursor.RowBuilder; 26 import android.net.Uri; 27 import android.telephony.ServiceState; 28 import android.telephony.SubscriptionManager; 29 import android.telephony.TelephonyManager; 30 import android.util.Log; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.telephony.Phone; 34 import com.android.internal.telephony.PhoneFactory; 35 import com.android.internal.telephony.SubscriptionController; 36 37 import java.lang.NumberFormatException; 38 import java.util.HashMap; 39 import java.util.Objects; 40 41 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId; 42 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField; 43 44 import static android.provider.Telephony.ServiceStateTable; 45 import static android.provider.Telephony.ServiceStateTable.CONTENT_URI; 46 47 import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE; 48 import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE; 49 import static android.provider.Telephony.ServiceStateTable.VOICE_ROAMING_TYPE; 50 import static android.provider.Telephony.ServiceStateTable.DATA_ROAMING_TYPE; 51 import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_LONG; 52 import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_SHORT; 53 import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_NUMERIC; 54 import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_LONG; 55 import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_SHORT; 56 import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_NUMERIC; 57 import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION; 58 import static android.provider.Telephony.ServiceStateTable.RIL_VOICE_RADIO_TECHNOLOGY; 59 import static android.provider.Telephony.ServiceStateTable.RIL_DATA_RADIO_TECHNOLOGY; 60 import static android.provider.Telephony.ServiceStateTable.CSS_INDICATOR; 61 import static android.provider.Telephony.ServiceStateTable.NETWORK_ID; 62 import static android.provider.Telephony.ServiceStateTable.SYSTEM_ID; 63 import static android.provider.Telephony.ServiceStateTable.CDMA_ROAMING_INDICATOR; 64 import static android.provider.Telephony.ServiceStateTable.CDMA_DEFAULT_ROAMING_INDICATOR; 65 import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_INDEX; 66 import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_MODE; 67 import static android.provider.Telephony.ServiceStateTable.IS_EMERGENCY_ONLY; 68 import static android.provider.Telephony.ServiceStateTable.IS_DATA_ROAMING_FROM_REGISTRATION; 69 import static android.provider.Telephony.ServiceStateTable.IS_USING_CARRIER_AGGREGATION; 70 71 72 public class ServiceStateProvider extends ContentProvider { 73 private static final String TAG = "ServiceStateProvider"; 74 75 public static final String AUTHORITY = ServiceStateTable.AUTHORITY; 76 public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); 77 78 private final HashMap<Integer, ServiceState> mServiceStates = new HashMap<>(); 79 private static final String[] sColumns = { 80 VOICE_REG_STATE, 81 DATA_REG_STATE, 82 VOICE_ROAMING_TYPE, 83 DATA_ROAMING_TYPE, 84 VOICE_OPERATOR_ALPHA_LONG, 85 VOICE_OPERATOR_ALPHA_SHORT, 86 VOICE_OPERATOR_NUMERIC, 87 DATA_OPERATOR_ALPHA_LONG, 88 DATA_OPERATOR_ALPHA_SHORT, 89 DATA_OPERATOR_NUMERIC, 90 IS_MANUAL_NETWORK_SELECTION, 91 RIL_VOICE_RADIO_TECHNOLOGY, 92 RIL_DATA_RADIO_TECHNOLOGY, 93 CSS_INDICATOR, 94 NETWORK_ID, 95 SYSTEM_ID, 96 CDMA_ROAMING_INDICATOR, 97 CDMA_DEFAULT_ROAMING_INDICATOR, 98 CDMA_ERI_ICON_INDEX, 99 CDMA_ERI_ICON_MODE, 100 IS_EMERGENCY_ONLY, 101 IS_DATA_ROAMING_FROM_REGISTRATION, 102 IS_USING_CARRIER_AGGREGATION, 103 }; 104 105 @Override 106 public boolean onCreate() { 107 return true; 108 } 109 110 @VisibleForTesting 111 public ServiceState getServiceState(int subId) { 112 return mServiceStates.get(subId); 113 } 114 115 @VisibleForTesting 116 public int getDefaultSubId() { 117 return SubscriptionController.getInstance().getDefaultSubId(); 118 } 119 120 @Override 121 public Uri insert(Uri uri, ContentValues values) { 122 if (uri.isPathPrefixMatch(CONTENT_URI)) { 123 // Parse the subId 124 int subId = 0; 125 try { 126 subId = Integer.parseInt(uri.getLastPathSegment()); 127 } catch (NumberFormatException e) { 128 Log.e(TAG, "insert: no subId provided in uri"); 129 throw e; 130 } 131 Log.d(TAG, "subId=" + subId); 132 133 // handle DEFAULT_SUBSCRIPTION_ID 134 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { 135 subId = getDefaultSubId(); 136 } 137 138 // create the new service state 139 ServiceState newSS = new ServiceState(); 140 newSS.setVoiceRegState(values.getAsInteger(VOICE_REG_STATE)); 141 newSS.setDataRegState(values.getAsInteger(DATA_REG_STATE)); 142 newSS.setVoiceOperatorName(values.getAsString(VOICE_OPERATOR_ALPHA_LONG), 143 values.getAsString(VOICE_OPERATOR_ALPHA_SHORT), 144 values.getAsString(VOICE_OPERATOR_NUMERIC)); 145 newSS.setDataOperatorName(values.getAsString(DATA_OPERATOR_ALPHA_LONG), 146 values.getAsString(DATA_OPERATOR_ALPHA_SHORT), 147 values.getAsString(DATA_OPERATOR_NUMERIC)); 148 newSS.setIsManualSelection(values.getAsBoolean(IS_MANUAL_NETWORK_SELECTION)); 149 newSS.setRilVoiceRadioTechnology(values.getAsInteger(RIL_VOICE_RADIO_TECHNOLOGY)); 150 newSS.setRilDataRadioTechnology(values.getAsInteger(RIL_DATA_RADIO_TECHNOLOGY)); 151 newSS.setCssIndicator(values.getAsInteger(CSS_INDICATOR)); 152 newSS.setSystemAndNetworkId(values.getAsInteger(SYSTEM_ID), 153 values.getAsInteger(NETWORK_ID)); 154 newSS.setCdmaRoamingIndicator(values.getAsInteger(CDMA_ROAMING_INDICATOR)); 155 newSS.setCdmaDefaultRoamingIndicator( 156 values.getAsInteger(CDMA_DEFAULT_ROAMING_INDICATOR)); 157 newSS.setCdmaEriIconIndex(values.getAsInteger(CDMA_ERI_ICON_INDEX)); 158 newSS.setCdmaEriIconMode(values.getAsInteger(CDMA_ERI_ICON_MODE)); 159 newSS.setEmergencyOnly(values.getAsBoolean(IS_EMERGENCY_ONLY)); 160 newSS.setDataRoamingFromRegistration( 161 values.getAsBoolean(IS_DATA_ROAMING_FROM_REGISTRATION)); 162 newSS.setIsUsingCarrierAggregation(values.getAsBoolean(IS_USING_CARRIER_AGGREGATION)); 163 164 // notify listeners 165 // if ss is null (e.g. first service state update) we will notify for all fields 166 ServiceState ss = getServiceState(subId); 167 notifyChangeForSubIdAndField(getContext(), ss, newSS, subId); 168 notifyChangeForSubId(getContext(), ss, newSS, subId); 169 170 // store the new service state 171 mServiceStates.put(subId, newSS); 172 return uri; 173 } 174 return null; 175 } 176 177 @Override 178 public int delete(Uri uri, String selection, String[] selectionArgs) { 179 throw new RuntimeException("Not supported"); 180 } 181 182 @Override 183 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 184 throw new RuntimeException("Not supported"); 185 } 186 187 @Override 188 public String getType(Uri uri) { 189 throw new RuntimeException("Not supported"); 190 } 191 192 @Override 193 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 194 String sortOrder) { 195 if (!uri.isPathPrefixMatch(CONTENT_URI)) { 196 throw new IllegalArgumentException("Invalid URI: " + uri); 197 } else { 198 // Parse the subId 199 int subId = 0; 200 try { 201 subId = Integer.parseInt(uri.getLastPathSegment()); 202 } catch (NumberFormatException e) { 203 Log.d(TAG, "query: no subId provided in uri, using default."); 204 subId = getDefaultSubId(); 205 } 206 Log.d(TAG, "subId=" + subId); 207 208 // handle DEFAULT_SUBSCRIPTION_ID 209 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { 210 subId = getDefaultSubId(); 211 } 212 213 // Get the service state 214 ServiceState ss = getServiceState(subId); 215 if (ss == null) { 216 Log.d(TAG, "returning null"); 217 return null; 218 } 219 220 // Build the result 221 final int voice_reg_state = ss.getVoiceRegState(); 222 final int data_reg_state = ss.getDataRegState(); 223 final int voice_roaming_type = ss.getVoiceRoamingType(); 224 final int data_roaming_type = ss.getDataRoamingType(); 225 final String voice_operator_alpha_long = ss.getVoiceOperatorAlphaLong(); 226 final String voice_operator_alpha_short = ss.getVoiceOperatorAlphaShort(); 227 final String voice_operator_numeric = ss.getVoiceOperatorNumeric(); 228 final String data_operator_alpha_long = ss.getDataOperatorAlphaLong(); 229 final String data_operator_alpha_short = ss.getDataOperatorAlphaShort(); 230 final String data_operator_numeric = ss.getDataOperatorNumeric(); 231 final int is_manual_network_selection = (ss.getIsManualSelection()) ? 1 : 0; 232 final int ril_voice_radio_technology = ss.getRilVoiceRadioTechnology(); 233 final int ril_data_radio_technology = ss.getRilDataRadioTechnology(); 234 final int css_indicator = ss.getCssIndicator(); 235 final int network_id = ss.getNetworkId(); 236 final int system_id = ss.getSystemId(); 237 final int cdma_roaming_indicator = ss.getCdmaRoamingIndicator(); 238 final int cdma_default_roaming_indicator = ss.getCdmaDefaultRoamingIndicator(); 239 final int cdma_eri_icon_index = ss.getCdmaEriIconIndex(); 240 final int cdma_eri_icon_mode = ss.getCdmaEriIconMode(); 241 final int is_emergency_only = (ss.isEmergencyOnly()) ? 1 : 0; 242 final int is_data_roaming_from_registration = 243 (ss.getDataRoamingFromRegistration()) ? 1 : 0; 244 final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0; 245 246 return buildSingleRowResult(projection, sColumns, new Object[] { 247 voice_reg_state, 248 data_reg_state, 249 voice_roaming_type, 250 data_roaming_type, 251 voice_operator_alpha_long, 252 voice_operator_alpha_short, 253 voice_operator_numeric, 254 data_operator_alpha_long, 255 data_operator_alpha_short, 256 data_operator_numeric, 257 is_manual_network_selection, 258 ril_voice_radio_technology, 259 ril_data_radio_technology, 260 css_indicator, 261 network_id, 262 system_id, 263 cdma_roaming_indicator, 264 cdma_default_roaming_indicator, 265 cdma_eri_icon_index, 266 cdma_eri_icon_mode, 267 is_emergency_only, 268 is_data_roaming_from_registration, 269 is_using_carrier_aggregation, 270 }); 271 } 272 } 273 274 private static Cursor buildSingleRowResult(String[] projection, String[] availableColumns, 275 Object[] data) { 276 if (projection == null) { 277 projection = availableColumns; 278 } 279 final MatrixCursor c = new MatrixCursor(projection, 1); 280 final RowBuilder row = c.newRow(); 281 for (int i = 0; i < c.getColumnCount(); i++) { 282 final String columnName = c.getColumnName(i); 283 boolean found = false; 284 for (int j = 0; j < availableColumns.length; j++) { 285 if (availableColumns[j].equals(columnName)) { 286 row.add(data[j]); 287 found = true; 288 break; 289 } 290 } 291 if (!found) { 292 throw new IllegalArgumentException("Invalid column " + projection[i]); 293 } 294 } 295 return c; 296 } 297 298 /** 299 * Notify interested apps that certain fields of the ServiceState have changed. 300 * 301 * Apps which want to wake when specific fields change can use 302 * JobScheduler's TriggerContentUri. This replaces the waking functionality of the implicit 303 * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targetting version O. 304 * 305 * We will only notify for certain fields. This is an intentional change from the behavior of 306 * the broadcast. Listeners will be notified when the voice or data registration state or 307 * roaming type changes. 308 */ 309 @VisibleForTesting 310 public static void notifyChangeForSubIdAndField(Context context, ServiceState oldSS, 311 ServiceState newSS, int subId) { 312 final boolean firstUpdate = (oldSS == null) ? true : false; 313 314 // for every field, if the field has changed values, notify via the provider 315 if (firstUpdate || voiceRegStateChanged(oldSS, newSS)) { 316 context.getContentResolver().notifyChange( 317 getUriForSubscriptionIdAndField(subId, VOICE_REG_STATE), 318 /* observer= */ null, /* syncToNetwork= */ false); 319 } 320 if (firstUpdate || dataRegStateChanged(oldSS, newSS)) { 321 context.getContentResolver().notifyChange( 322 getUriForSubscriptionIdAndField(subId, DATA_REG_STATE), null, false); 323 } 324 if (firstUpdate || voiceRoamingTypeChanged(oldSS, newSS)) { 325 context.getContentResolver().notifyChange( 326 getUriForSubscriptionIdAndField(subId, VOICE_ROAMING_TYPE), null, false); 327 } 328 if (firstUpdate || dataRoamingTypeChanged(oldSS, newSS)) { 329 context.getContentResolver().notifyChange( 330 getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false); 331 } 332 } 333 334 private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) { 335 return oldSS.getVoiceRegState() != newSS.getVoiceRegState(); 336 } 337 338 private static boolean dataRegStateChanged(ServiceState oldSS, ServiceState newSS) { 339 return oldSS.getDataRegState() != newSS.getDataRegState(); 340 } 341 342 private static boolean voiceRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) { 343 return oldSS.getVoiceRoamingType() != newSS.getVoiceRoamingType(); 344 } 345 346 private static boolean dataRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) { 347 return oldSS.getDataRoamingType() != newSS.getDataRoamingType(); 348 } 349 350 /** 351 * Notify interested apps that the ServiceState has changed. 352 * 353 * Apps which want to wake when any field in the ServiceState has changed can use 354 * JobScheduler's TriggerContentUri. This replaces the waking functionality of the implicit 355 * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targetting version O. 356 * 357 * We will only notify for certain fields. This is an intentional change from the behavior of 358 * the broadcast. Listeners will be notified when the voice or data registration state or 359 * roaming type changes. 360 */ 361 @VisibleForTesting 362 public static void notifyChangeForSubId(Context context, ServiceState oldSS, ServiceState newSS, 363 int subId) { 364 // if the voice or data registration or roaming state field has changed values, notify via 365 // the provider. 366 // If oldSS is null and newSS is not (e.g. first update of service state) this will also 367 // notify 368 if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS) 369 || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)) { 370 context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false); 371 } 372 } 373 } 374