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