blob: 3fa1e5834e007d002b349278667022a930b99948 [file] [log] [blame]
SongFerngWang1bb5a6f2019-12-10 00:42:54 +08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
Rambo Wang979ccae2021-04-28 17:51:36 -070019import static android.content.pm.PackageManager.PERMISSION_GRANTED;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080020import static android.provider.Telephony.ServiceStateTable;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080021import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
Rambo Wangfbdb3482021-03-15 21:14:19 -070022import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
Rambo Wang191a6812021-03-15 11:41:49 -070023import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
Rambo Wang917ee2f2021-03-15 12:15:51 -070024import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080025import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080026import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080027import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
28import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField;
29
Rambo Wang979ccae2021-04-28 17:51:36 -070030import android.Manifest;
Rambo Wang1bf0afc2021-06-25 11:53:44 -070031import android.app.compat.CompatChanges;
32import android.compat.annotation.ChangeId;
33import android.compat.annotation.EnabledAfter;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080034import android.content.ContentProvider;
35import android.content.ContentValues;
36import android.content.Context;
37import android.database.Cursor;
38import android.database.MatrixCursor;
39import android.database.MatrixCursor.RowBuilder;
40import android.net.Uri;
Rambo Wang979ccae2021-04-28 17:51:36 -070041import android.os.Binder;
42import android.os.Build;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080043import android.os.Parcel;
Rambo Wang979ccae2021-04-28 17:51:36 -070044import android.telephony.LocationAccessPolicy;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080045import android.telephony.ServiceState;
46import android.telephony.SubscriptionManager;
Rambo Wang88c36272021-06-23 13:02:34 -070047import android.telephony.TelephonyManager;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080048import android.util.Log;
49
50import com.android.internal.annotations.VisibleForTesting;
Rambo Wang979ccae2021-04-28 17:51:36 -070051import com.android.internal.telephony.TelephonyPermissions;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080052
53import java.util.HashMap;
Grace Jia1ca70d02020-01-16 14:13:12 -080054import java.util.List;
55import java.util.Objects;
Rambo Wang979ccae2021-04-28 17:51:36 -070056import java.util.Set;
SongFerngWang1bb5a6f2019-12-10 00:42:54 +080057
58/**
59 * The class to provide base facility to access ServiceState related content,
60 * which is stored in a SQLite database.
61 */
62public class ServiceStateProvider extends ContentProvider {
63 private static final String TAG = "ServiceStateProvider";
64
65 public static final String AUTHORITY = ServiceStateTable.AUTHORITY;
66 public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
67
Grace Jiadd25f0e2020-01-21 12:35:28 -080068 /**
69 * The current service state.
70 *
71 * This is the entire {@link ServiceState} object in byte array.
72 *
73 * @hide
74 */
75 public static final String SERVICE_STATE = "service_state";
76
77 /**
Grace Jiadd25f0e2020-01-21 12:35:28 -080078 * An integer value indicating the current voice roaming type.
79 * <p>
80 * This is the same as {@link ServiceState#getVoiceRoamingType()}.
81 * @hide
82 */
83 public static final String VOICE_ROAMING_TYPE = "voice_roaming_type";
84
85 /**
86 * An integer value indicating the current data roaming type.
87 * <p>
88 * This is the same as {@link ServiceState#getDataRoamingType()}.
89 * @hide
90 */
91 public static final String DATA_ROAMING_TYPE = "data_roaming_type";
92
93 /**
94 * The current registered voice network operator name in long alphanumeric format.
95 * <p>
96 * This is the same as {@link ServiceState#getOperatorAlphaLong()}.
97 * @hide
98 */
99 public static final String VOICE_OPERATOR_ALPHA_LONG = "voice_operator_alpha_long";
100
101 /**
102 * The current registered operator name in short alphanumeric format.
103 * <p>
104 * In GSM/UMTS, short format can be up to 8 characters long. The current registered voice
105 * network operator name in long alphanumeric format.
106 * <p>
107 * This is the same as {@link ServiceState#getOperatorAlphaShort()}.
108 * @hide
109 */
110 public static final String VOICE_OPERATOR_ALPHA_SHORT = "voice_operator_alpha_short";
111
112 /**
113 * The current registered operator numeric id.
114 * <p>
115 * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
116 * network code.
117 * <p>
118 * This is the same as {@link ServiceState#getOperatorNumeric()}.
119 */
120 public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
121
122 /**
123 * The current registered data network operator name in long alphanumeric format.
124 * <p>
125 * This is the same as {@link ServiceState#getOperatorAlphaLong()}.
126 * @hide
127 */
128 public static final String DATA_OPERATOR_ALPHA_LONG = "data_operator_alpha_long";
129
130 /**
131 * The current registered data network operator name in short alphanumeric format.
132 * <p>
133 * This is the same as {@link ServiceState#getOperatorAlphaShort()}.
134 * @hide
135 */
136 public static final String DATA_OPERATOR_ALPHA_SHORT = "data_operator_alpha_short";
137
138 /**
139 * The current registered data network operator numeric id.
140 * <p>
141 * This is the same as {@link ServiceState#getOperatorNumeric()}.
142 * @hide
143 */
144 public static final String DATA_OPERATOR_NUMERIC = "data_operator_numeric";
145
146 /**
147 * This is the same as {@link ServiceState#getRilVoiceRadioTechnology()}.
148 * @hide
149 */
150 public static final String RIL_VOICE_RADIO_TECHNOLOGY = "ril_voice_radio_technology";
151
152 /**
153 * This is the same as {@link ServiceState#getRilDataRadioTechnology()}.
154 * @hide
155 */
156 public static final String RIL_DATA_RADIO_TECHNOLOGY = "ril_data_radio_technology";
157
158 /**
159 * This is the same as {@link ServiceState#getCssIndicator()}.
160 * @hide
161 */
162 public static final String CSS_INDICATOR = "css_indicator";
163
164 /**
165 * This is the same as {@link ServiceState#getCdmaNetworkId()}.
166 * @hide
167 */
168 public static final String NETWORK_ID = "network_id";
169
170 /**
171 * This is the same as {@link ServiceState#getCdmaSystemId()}.
172 * @hide
173 */
174 public static final String SYSTEM_ID = "system_id";
175
176 /**
177 * This is the same as {@link ServiceState#getCdmaRoamingIndicator()}.
178 * @hide
179 */
180 public static final String CDMA_ROAMING_INDICATOR = "cdma_roaming_indicator";
181
182 /**
183 * This is the same as {@link ServiceState#getCdmaDefaultRoamingIndicator()}.
184 * @hide
185 */
186 public static final String CDMA_DEFAULT_ROAMING_INDICATOR =
187 "cdma_default_roaming_indicator";
188
189 /**
190 * This is the same as {@link ServiceState#getCdmaEriIconIndex()}.
191 * @hide
192 */
193 public static final String CDMA_ERI_ICON_INDEX = "cdma_eri_icon_index";
194
195 /**
196 * This is the same as {@link ServiceState#getCdmaEriIconMode()}.
197 * @hide
198 */
199 public static final String CDMA_ERI_ICON_MODE = "cdma_eri_icon_mode";
200
201 /**
202 * This is the same as {@link ServiceState#isEmergencyOnly()}.
203 * @hide
204 */
205 public static final String IS_EMERGENCY_ONLY = "is_emergency_only";
206
207 /**
208 * This is the same as {@link ServiceState#getDataRoamingFromRegistration()}.
209 * @hide
210 */
211 public static final String IS_DATA_ROAMING_FROM_REGISTRATION =
212 "is_data_roaming_from_registration";
213
214 /**
215 * This is the same as {@link ServiceState#isUsingCarrierAggregation()}.
216 * @hide
217 */
218 public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
219
220 /**
221 * The current registered raw data network operator name in long alphanumeric format.
222 * <p>
223 * This is the same as {@link ServiceState#getOperatorAlphaLongRaw()}.
224 * @hide
225 */
226 public static final String OPERATOR_ALPHA_LONG_RAW = "operator_alpha_long_raw";
227
228 /**
229 * The current registered raw data network operator name in short alphanumeric format.
230 * <p>
231 * This is the same as {@link ServiceState#getOperatorAlphaShortRaw()}.
232 * @hide
233 */
234 public static final String OPERATOR_ALPHA_SHORT_RAW = "operator_alpha_short_raw";
235
Rambo Wang1bf0afc2021-06-25 11:53:44 -0700236 /**
237 * If the change Id is enabled, location permission is required to access location sensitive
238 * columns in the ServiceStateTable.
239 */
240 @ChangeId
241 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
242 @VisibleForTesting
243 /* package */ static final long ENFORCE_LOCATION_PERMISSION_CHECK = 191911306;
244
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800245 private final HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
Rambo Wang979ccae2021-04-28 17:51:36 -0700246
247 @VisibleForTesting
248 /* package */ static final String[] ALL_COLUMNS = {
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800249 VOICE_REG_STATE,
250 DATA_REG_STATE,
251 VOICE_ROAMING_TYPE,
252 DATA_ROAMING_TYPE,
253 VOICE_OPERATOR_ALPHA_LONG,
254 VOICE_OPERATOR_ALPHA_SHORT,
255 VOICE_OPERATOR_NUMERIC,
256 DATA_OPERATOR_ALPHA_LONG,
257 DATA_OPERATOR_ALPHA_SHORT,
258 DATA_OPERATOR_NUMERIC,
259 IS_MANUAL_NETWORK_SELECTION,
260 RIL_VOICE_RADIO_TECHNOLOGY,
261 RIL_DATA_RADIO_TECHNOLOGY,
262 CSS_INDICATOR,
263 NETWORK_ID,
264 SYSTEM_ID,
265 CDMA_ROAMING_INDICATOR,
266 CDMA_DEFAULT_ROAMING_INDICATOR,
267 CDMA_ERI_ICON_INDEX,
268 CDMA_ERI_ICON_MODE,
269 IS_EMERGENCY_ONLY,
270 IS_USING_CARRIER_AGGREGATION,
271 OPERATOR_ALPHA_LONG_RAW,
272 OPERATOR_ALPHA_SHORT_RAW,
Rambo Wangfbdb3482021-03-15 21:14:19 -0700273 DATA_NETWORK_TYPE,
Rambo Wang917ee2f2021-03-15 12:15:51 -0700274 DUPLEX_MODE,
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800275 };
276
Rambo Wang979ccae2021-04-28 17:51:36 -0700277 /**
278 * Columns that are exposed to public surface.
279 * These are the columns accessible to apps target S+ and lack
280 * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} permission.
281 */
282 @VisibleForTesting
283 /* package */ static final String[] PUBLIC_COLUMNS = {
284 VOICE_REG_STATE,
285 DATA_REG_STATE,
286 VOICE_OPERATOR_NUMERIC,
287 IS_MANUAL_NETWORK_SELECTION,
288 DATA_NETWORK_TYPE,
289 DUPLEX_MODE
290 };
291
292 /**
293 * Columns protected by location permissions (either FINE or COARSE).
294 * SecurityException will throw if applications without location permissions try to put those
295 * columns explicitly into cursor (e.g. through {@code projection} parameter in
296 * {@link #query(Uri, String[], String, String[], String)} method).
297 * Default (scrub-out) value will return if applications try to put all columns into cursor by
298 * specifying null of {@code projection} parameter and get values through the returned cursor.
299 */
300 private static final Set<String> LOCATION_PROTECTED_COLUMNS_SET = Set.of(
301 NETWORK_ID,
302 SYSTEM_ID
303 );
304
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800305 @Override
306 public boolean onCreate() {
307 return true;
308 }
309
310 /**
311 * Returns the {@link ServiceState} information on specified subscription.
312 *
313 * @param subId whose subscriber id is returned
314 * @return the {@link ServiceState} information on specified subscription.
315 */
316 @VisibleForTesting
317 public ServiceState getServiceState(int subId) {
318 return mServiceStates.get(subId);
319 }
320
321 /**
322 * Returns the system's default subscription id.
323 *
324 * @return the "system" default subscription id.
325 */
326 @VisibleForTesting
327 public int getDefaultSubId() {
328 return SubscriptionManager.getDefaultSubscriptionId();
329 }
330
331 @Override
332 public Uri insert(Uri uri, ContentValues values) {
Grace Jia1ca70d02020-01-16 14:13:12 -0800333 if (isPathPrefixMatch(uri, CONTENT_URI)) {
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800334 // Parse the subId
335 int subId = 0;
336 try {
337 subId = Integer.parseInt(uri.getLastPathSegment());
338 } catch (NumberFormatException e) {
339 Log.e(TAG, "insert: no subId provided in uri");
340 throw e;
341 }
342 Log.d(TAG, "subId=" + subId);
343
344 // handle DEFAULT_SUBSCRIPTION_ID
345 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
346 subId = getDefaultSubId();
347 }
348
349 final Parcel p = Parcel.obtain();
350 final byte[] rawBytes = values.getAsByteArray(SERVICE_STATE);
351 p.unmarshall(rawBytes, 0, rawBytes.length);
352 p.setDataPosition(0);
353
354 // create the new service state
355 final ServiceState newSS = ServiceState.CREATOR.createFromParcel(p);
356
357 // notify listeners
358 // if ss is null (e.g. first service state update) we will notify for all fields
359 ServiceState ss = getServiceState(subId);
360 notifyChangeForSubIdAndField(getContext(), ss, newSS, subId);
361 notifyChangeForSubId(getContext(), ss, newSS, subId);
362
363 // store the new service state
364 mServiceStates.put(subId, newSS);
365 return uri;
366 }
367 return null;
368 }
369
370 @Override
371 public int delete(Uri uri, String selection, String[] selectionArgs) {
372 throw new RuntimeException("Not supported");
373 }
374
375 @Override
376 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
377 throw new RuntimeException("Not supported");
378 }
379
380 @Override
381 public String getType(Uri uri) {
382 throw new RuntimeException("Not supported");
383 }
384
385 @Override
386 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
387 String sortOrder) {
Grace Jia1ca70d02020-01-16 14:13:12 -0800388 if (!isPathPrefixMatch(uri, CONTENT_URI)) {
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800389 throw new IllegalArgumentException("Invalid URI: " + uri);
390 } else {
391 // Parse the subId
392 int subId = 0;
393 try {
394 subId = Integer.parseInt(uri.getLastPathSegment());
395 } catch (NumberFormatException e) {
396 Log.d(TAG, "query: no subId provided in uri, using default.");
397 subId = getDefaultSubId();
398 }
399 Log.d(TAG, "subId=" + subId);
400
401 // handle DEFAULT_SUBSCRIPTION_ID
402 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
403 subId = getDefaultSubId();
404 }
405
406 // Get the service state
Rambo Wang979ccae2021-04-28 17:51:36 -0700407 ServiceState unredactedServiceState = getServiceState(subId);
408 if (unredactedServiceState == null) {
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800409 Log.d(TAG, "returning null");
410 return null;
411 }
412
Rambo Wang1bf0afc2021-06-25 11:53:44 -0700413 final boolean enforceLocationPermission =
414 CompatChanges.isChangeEnabled(ENFORCE_LOCATION_PERMISSION_CHECK);
Rambo Wang979ccae2021-04-28 17:51:36 -0700415 final boolean targetingAtLeastS = TelephonyPermissions.getTargetSdk(getContext(),
416 getCallingPackage()) >= Build.VERSION_CODES.S;
417 final boolean canReadPrivilegedPhoneState = getContext().checkCallingOrSelfPermission(
418 Manifest.permission.READ_PRIVILEGED_PHONE_STATE) == PERMISSION_GRANTED;
419
420 final String[] availableColumns;
421 final ServiceState ss;
Rambo Wang1bf0afc2021-06-25 11:53:44 -0700422 if (enforceLocationPermission && targetingAtLeastS && !canReadPrivilegedPhoneState) {
Rambo Wang979ccae2021-04-28 17:51:36 -0700423 // targetSdkVersion S+ without read privileged phone state permission can only
424 // access public columns which have no location sensitive info.
425 availableColumns = PUBLIC_COLUMNS;
426 ss = unredactedServiceState;
427 } else {
428 availableColumns = ALL_COLUMNS;
Rambo Wang353652c2022-03-02 12:22:21 -0800429 if (!enforceLocationPermission) {
430 // No matter the targetSdkVersion, return unredacted ServiceState if location
431 // permission enforcement is not introduced
Rambo Wang979ccae2021-04-28 17:51:36 -0700432 ss = unredactedServiceState;
433 } else {
Rambo Wang353652c2022-03-02 12:22:21 -0800434 boolean implicitlyQueryLocation = projection == null;
435 boolean explicitlyQueryLocation = false;
436 if (projection != null) {
Rambo Wang979ccae2021-04-28 17:51:36 -0700437 for (String requiredColumn : projection) {
438 if (LOCATION_PROTECTED_COLUMNS_SET.contains(requiredColumn)) {
Rambo Wang353652c2022-03-02 12:22:21 -0800439 explicitlyQueryLocation = true;
440 break;
Rambo Wang979ccae2021-04-28 17:51:36 -0700441 }
442 }
443 }
444
Rambo Wang353652c2022-03-02 12:22:21 -0800445 // Check location permission only when location sensitive info are queried
446 // (either explicitly or implicitly) to avoid caller get blamed with location
447 // permission when query non sensitive info.
448 if (implicitlyQueryLocation || explicitlyQueryLocation) {
449 if (hasLocationPermission()) {
450 ss = unredactedServiceState;
451 } else {
452 if (targetingAtLeastS) {
453 // Throw SecurityException to fail loudly if caller is targetSDK S+
454 throw new SecurityException(
455 "Querying location sensitive info requires location "
456 + "permissions");
457 } else {
458 // For backward compatibility, return redacted value for old SDK
459 ss = getLocationRedactedServiceState(unredactedServiceState);
460 }
461 }
462 } else {
463 // The caller is not interested in location sensitive info, return result
464 // that scrub out all sensitive info. And no permission check is needed.
465 ss = getLocationRedactedServiceState(unredactedServiceState);
466 }
Rambo Wang979ccae2021-04-28 17:51:36 -0700467 }
468 }
469
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800470 // Build the result
SongFerngWangf379b8b2019-12-23 18:24:44 +0800471 final int voice_reg_state = ss.getState();
472 final int data_reg_state = ss.getDataRegistrationState();
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800473 final int voice_roaming_type = ss.getVoiceRoamingType();
474 final int data_roaming_type = ss.getDataRoamingType();
475 final String voice_operator_alpha_long = ss.getOperatorAlphaLong();
476 final String voice_operator_alpha_short = ss.getOperatorAlphaShort();
477 final String voice_operator_numeric = ss.getOperatorNumeric();
478 final String data_operator_alpha_long = ss.getOperatorAlphaLong();
479 final String data_operator_alpha_short = ss.getOperatorAlphaShort();
480 final String data_operator_numeric = ss.getOperatorNumeric();
481 final int is_manual_network_selection = (ss.getIsManualSelection()) ? 1 : 0;
482 final int ril_voice_radio_technology = ss.getRilVoiceRadioTechnology();
483 final int ril_data_radio_technology = ss.getRilDataRadioTechnology();
484 final int css_indicator = ss.getCssIndicator();
485 final int network_id = ss.getCdmaNetworkId();
486 final int system_id = ss.getCdmaSystemId();
487 final int cdma_roaming_indicator = ss.getCdmaRoamingIndicator();
488 final int cdma_default_roaming_indicator = ss.getCdmaDefaultRoamingIndicator();
489 final int cdma_eri_icon_index = ss.getCdmaEriIconIndex();
490 final int cdma_eri_icon_mode = ss.getCdmaEriIconMode();
491 final int is_emergency_only = (ss.isEmergencyOnly()) ? 1 : 0;
492 final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
493 final String operator_alpha_long_raw = ss.getOperatorAlphaLongRaw();
494 final String operator_alpha_short_raw = ss.getOperatorAlphaShortRaw();
Rambo Wangfbdb3482021-03-15 21:14:19 -0700495 final int data_network_type = ss.getDataNetworkType();
Rambo Wang917ee2f2021-03-15 12:15:51 -0700496 final int duplex_mode = ss.getDuplexMode();
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800497
Rambo Wang979ccae2021-04-28 17:51:36 -0700498 Object[] data = availableColumns == ALL_COLUMNS ? new Object[]{
499 // data for all columns
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800500 voice_reg_state,
501 data_reg_state,
502 voice_roaming_type,
503 data_roaming_type,
504 voice_operator_alpha_long,
505 voice_operator_alpha_short,
506 voice_operator_numeric,
507 data_operator_alpha_long,
508 data_operator_alpha_short,
509 data_operator_numeric,
510 is_manual_network_selection,
511 ril_voice_radio_technology,
512 ril_data_radio_technology,
513 css_indicator,
514 network_id,
515 system_id,
516 cdma_roaming_indicator,
517 cdma_default_roaming_indicator,
518 cdma_eri_icon_index,
519 cdma_eri_icon_mode,
520 is_emergency_only,
521 is_using_carrier_aggregation,
522 operator_alpha_long_raw,
523 operator_alpha_short_raw,
Rambo Wangfbdb3482021-03-15 21:14:19 -0700524 data_network_type,
Rambo Wang917ee2f2021-03-15 12:15:51 -0700525 duplex_mode,
Rambo Wang979ccae2021-04-28 17:51:36 -0700526 } : new Object[]{
527 // data for public columns only
528 voice_reg_state,
529 data_reg_state,
530 voice_operator_numeric,
531 is_manual_network_selection,
532 data_network_type,
533 duplex_mode,
534 };
535
536 return buildSingleRowResult(projection, availableColumns, data);
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800537 }
538 }
539
540 private static Cursor buildSingleRowResult(String[] projection, String[] availableColumns,
541 Object[] data) {
542 if (projection == null) {
543 projection = availableColumns;
544 }
545 final MatrixCursor c = new MatrixCursor(projection, 1);
546 final RowBuilder row = c.newRow();
547 for (int i = 0; i < c.getColumnCount(); i++) {
548 final String columnName = c.getColumnName(i);
549 boolean found = false;
550 for (int j = 0; j < availableColumns.length; j++) {
551 if (availableColumns[j].equals(columnName)) {
552 row.add(data[j]);
553 found = true;
554 break;
555 }
556 }
557 if (!found) {
558 throw new IllegalArgumentException("Invalid column " + projection[i]);
559 }
560 }
561 return c;
562 }
563
564 /**
565 * Notify interested apps that certain fields of the ServiceState have changed.
566 *
567 * Apps which want to wake when specific fields change can use
568 * JobScheduler's TriggerContentUri. This replaces the waking functionality of the implicit
569 * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targeting version O.
570 *
571 * We will only notify for certain fields. This is an intentional change from the behavior of
572 * the broadcast. Listeners will be notified when the voice or data registration state or
573 * roaming type changes.
574 */
575 @VisibleForTesting
576 public static void notifyChangeForSubIdAndField(Context context, ServiceState oldSS,
577 ServiceState newSS, int subId) {
578 final boolean firstUpdate = (oldSS == null) ? true : false;
579
580 // for every field, if the field has changed values, notify via the provider
581 if (firstUpdate || voiceRegStateChanged(oldSS, newSS)) {
582 context.getContentResolver().notifyChange(
583 getUriForSubscriptionIdAndField(subId, VOICE_REG_STATE),
584 /* observer= */ null, /* syncToNetwork= */ false);
585 }
586 if (firstUpdate || dataRegStateChanged(oldSS, newSS)) {
587 context.getContentResolver().notifyChange(
588 getUriForSubscriptionIdAndField(subId, DATA_REG_STATE), null, false);
589 }
590 if (firstUpdate || voiceRoamingTypeChanged(oldSS, newSS)) {
591 context.getContentResolver().notifyChange(
592 getUriForSubscriptionIdAndField(subId, VOICE_ROAMING_TYPE), null, false);
593 }
594 if (firstUpdate || dataRoamingTypeChanged(oldSS, newSS)) {
595 context.getContentResolver().notifyChange(
596 getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false);
597 }
Rambo Wangfbdb3482021-03-15 21:14:19 -0700598 if (firstUpdate || dataNetworkTypeChanged(oldSS, newSS)) {
599 context.getContentResolver().notifyChange(
600 getUriForSubscriptionIdAndField(subId, DATA_NETWORK_TYPE), null, false);
601 }
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800602 }
603
604 private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) {
SongFerngWangf379b8b2019-12-23 18:24:44 +0800605 return oldSS.getState() != newSS.getState();
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800606 }
607
608 private static boolean dataRegStateChanged(ServiceState oldSS, ServiceState newSS) {
SongFerngWangf379b8b2019-12-23 18:24:44 +0800609 return oldSS.getDataRegistrationState() != newSS.getDataRegistrationState();
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800610 }
611
612 private static boolean voiceRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
613 return oldSS.getVoiceRoamingType() != newSS.getVoiceRoamingType();
614 }
615
616 private static boolean dataRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
617 return oldSS.getDataRoamingType() != newSS.getDataRoamingType();
618 }
619
Rambo Wangfbdb3482021-03-15 21:14:19 -0700620 private static boolean dataNetworkTypeChanged(ServiceState oldSS, ServiceState newSS) {
621 return oldSS.getDataNetworkType() != newSS.getDataNetworkType();
622 }
623
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800624 /**
625 * Notify interested apps that the ServiceState has changed.
626 *
627 * Apps which want to wake when any field in the ServiceState has changed can use
628 * JobScheduler's TriggerContentUri. This replaces the waking functionality of the implicit
629 * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targeting version O.
630 *
631 * We will only notify for certain fields. This is an intentional change from the behavior of
632 * the broadcast. Listeners will only be notified when the voice/data registration state or
633 * roaming type changes.
634 */
635 @VisibleForTesting
636 public static void notifyChangeForSubId(Context context, ServiceState oldSS, ServiceState newSS,
637 int subId) {
638 // if the voice or data registration or roaming state field has changed values, notify via
639 // the provider.
640 // If oldSS is null and newSS is not (e.g. first update of service state) this will also
641 // notify
642 if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS)
Rambo Wangfbdb3482021-03-15 21:14:19 -0700643 || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)
644 || dataNetworkTypeChanged(oldSS, newSS)) {
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800645 context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
646 }
647 }
Grace Jia1ca70d02020-01-16 14:13:12 -0800648
649 /**
650 * Test if this is a path prefix match against the given Uri. Verifies that
651 * scheme, authority, and atomic path segments match.
652 *
653 * Copied from frameworks/base/core/java/android/net/Uri.java
654 */
655 private boolean isPathPrefixMatch(Uri uriA, Uri uriB) {
656 if (!Objects.equals(uriA.getScheme(), uriB.getScheme())) return false;
657 if (!Objects.equals(uriA.getAuthority(), uriB.getAuthority())) return false;
658
659 List<String> segA = uriA.getPathSegments();
660 List<String> segB = uriB.getPathSegments();
661
662 final int size = segB.size();
663 if (segA.size() < size) return false;
664
665 for (int i = 0; i < size; i++) {
666 if (!Objects.equals(segA.get(i), segB.get(i))) {
667 return false;
668 }
669 }
670
671 return true;
672 }
Grace Jiadd25f0e2020-01-21 12:35:28 -0800673
674 /**
675 * Used to insert a ServiceState into the ServiceStateProvider as a ContentValues instance.
676 *
677 * @param state the ServiceState to convert into ContentValues
678 * @return the convertedContentValues instance
679 * @hide
680 */
681 public static ContentValues getContentValuesForServiceState(ServiceState state) {
682 ContentValues values = new ContentValues();
683 final Parcel p = Parcel.obtain();
684 state.writeToParcel(p, 0);
685 // Turn the parcel to byte array. Safe to do this because the content values were never
686 // written into a persistent storage. ServiceStateProvider keeps values in the memory.
687 values.put(SERVICE_STATE, p.marshall());
688 return values;
689 }
Rambo Wang979ccae2021-04-28 17:51:36 -0700690
Rambo Wang88c36272021-06-23 13:02:34 -0700691 /**
692 * Check location permission with same policy as {@link TelephonyManager#getServiceState()}
693 * which enforces location permission check starting from Q.
694 */
695 private boolean hasLocationPermission() {
696 LocationAccessPolicy.LocationPermissionResult locationPermissionResult =
Rambo Wang979ccae2021-04-28 17:51:36 -0700697 LocationAccessPolicy.checkLocationPermission(getContext(),
698 new LocationAccessPolicy.LocationPermissionQuery.Builder()
699 .setCallingPackage(getCallingPackage())
700 .setCallingFeatureId(getCallingAttributionTag())
701 .setCallingPid(Binder.getCallingPid())
702 .setCallingUid(Binder.getCallingUid())
703 .setMethod("ServiceStateProvider#query")
704 .setLogAsInfo(true)
Rambo Wang88c36272021-06-23 13:02:34 -0700705 .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
706 .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
707 .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
Rambo Wang979ccae2021-04-28 17:51:36 -0700708 .build());
Rambo Wang88c36272021-06-23 13:02:34 -0700709 return locationPermissionResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
Rambo Wang979ccae2021-04-28 17:51:36 -0700710 }
711
Rambo Wang88c36272021-06-23 13:02:34 -0700712 // Return a copy of ServiceState with all sensitive info redacted.
713 @VisibleForTesting
714 /* package */ static ServiceState getLocationRedactedServiceState(ServiceState serviceState) {
715 ServiceState ss =
716 serviceState.createLocationInfoSanitizedCopy(true /*removeCoarseLocation*/);
Rambo Wang88c36272021-06-23 13:02:34 -0700717 return ss;
Rambo Wang979ccae2021-04-28 17:51:36 -0700718 }
SongFerngWang1bb5a6f2019-12-10 00:42:54 +0800719}