• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.phone;
18 
19 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
20 import static android.provider.Telephony.ServiceStateTable;
21 import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
22 import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
23 import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
24 import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
25 import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
26 import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
27 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
28 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField;
29 
30 import android.Manifest;
31 import android.app.compat.CompatChanges;
32 import android.compat.annotation.ChangeId;
33 import android.compat.annotation.EnabledAfter;
34 import android.content.ContentProvider;
35 import android.content.ContentValues;
36 import android.content.Context;
37 import android.database.Cursor;
38 import android.database.MatrixCursor;
39 import android.database.MatrixCursor.RowBuilder;
40 import android.net.Uri;
41 import android.os.Binder;
42 import android.os.Build;
43 import android.os.Parcel;
44 import android.telephony.LocationAccessPolicy;
45 import android.telephony.ServiceState;
46 import android.telephony.SubscriptionManager;
47 import android.telephony.TelephonyManager;
48 import android.util.Log;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.telephony.TelephonyPermissions;
52 
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Objects;
56 import java.util.Set;
57 
58 /**
59  * The class to provide base facility to access ServiceState related content,
60  * which is stored in a SQLite database.
61  */
62 public 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 
68     /**
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     /**
78      * 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 
236     /**
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 
245     private final HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
246 
247     @VisibleForTesting
248     /* package */ static final String[] ALL_COLUMNS = {
249         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,
273         DATA_NETWORK_TYPE,
274         DUPLEX_MODE,
275     };
276 
277     /**
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 
305     @Override
onCreate()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
getServiceState(int subId)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
getDefaultSubId()327     public int getDefaultSubId() {
328         return SubscriptionManager.getDefaultSubscriptionId();
329     }
330 
331     @Override
insert(Uri uri, ContentValues values)332     public Uri insert(Uri uri, ContentValues values) {
333         if (isPathPrefixMatch(uri, CONTENT_URI)) {
334             // 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
delete(Uri uri, String selection, String[] selectionArgs)371     public int delete(Uri uri, String selection, String[] selectionArgs) {
372         throw new RuntimeException("Not supported");
373     }
374 
375     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)376     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
377         throw new RuntimeException("Not supported");
378     }
379 
380     @Override
getType(Uri uri)381     public String getType(Uri uri) {
382         throw new RuntimeException("Not supported");
383     }
384 
385     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)386     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
387             String sortOrder) {
388         if (!isPathPrefixMatch(uri, CONTENT_URI)) {
389             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
407             ServiceState unredactedServiceState = getServiceState(subId);
408             if (unredactedServiceState == null) {
409                 Log.d(TAG, "returning null");
410                 return null;
411             }
412 
413             final boolean enforceLocationPermission =
414                     CompatChanges.isChangeEnabled(ENFORCE_LOCATION_PERMISSION_CHECK);
415             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;
422             if (enforceLocationPermission && targetingAtLeastS && !canReadPrivilegedPhoneState) {
423                 // 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;
429                 if (!enforceLocationPermission) {
430                     // No matter the targetSdkVersion, return unredacted ServiceState if location
431                     // permission enforcement is not introduced
432                     ss = unredactedServiceState;
433                 } else {
434                     boolean implicitlyQueryLocation = projection == null;
435                     boolean explicitlyQueryLocation = false;
436                     if (projection != null) {
437                         for (String requiredColumn : projection) {
438                             if (LOCATION_PROTECTED_COLUMNS_SET.contains(requiredColumn)) {
439                                 explicitlyQueryLocation = true;
440                                 break;
441                             }
442                         }
443                     }
444 
445                     // 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                     }
467                 }
468             }
469 
470             // Build the result
471             final int voice_reg_state = ss.getState();
472             final int data_reg_state = ss.getDataRegistrationState();
473             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();
495             final int data_network_type = ss.getDataNetworkType();
496             final int duplex_mode = ss.getDuplexMode();
497 
498             Object[] data = availableColumns == ALL_COLUMNS ? new Object[]{
499                     // data for all columns
500                     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,
524                     data_network_type,
525                     duplex_mode,
526             } : 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);
537         }
538     }
539 
buildSingleRowResult(String[] projection, String[] availableColumns, Object[] data)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
notifyChangeForSubIdAndField(Context context, ServiceState oldSS, ServiceState newSS, int subId)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         }
598         if (firstUpdate || dataNetworkTypeChanged(oldSS, newSS)) {
599             context.getContentResolver().notifyChange(
600                     getUriForSubscriptionIdAndField(subId, DATA_NETWORK_TYPE), null, false);
601         }
602     }
603 
voiceRegStateChanged(ServiceState oldSS, ServiceState newSS)604     private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) {
605         return oldSS.getState() != newSS.getState();
606     }
607 
dataRegStateChanged(ServiceState oldSS, ServiceState newSS)608     private static boolean dataRegStateChanged(ServiceState oldSS, ServiceState newSS) {
609         return oldSS.getDataRegistrationState() != newSS.getDataRegistrationState();
610     }
611 
voiceRoamingTypeChanged(ServiceState oldSS, ServiceState newSS)612     private static boolean voiceRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
613         return oldSS.getVoiceRoamingType() != newSS.getVoiceRoamingType();
614     }
615 
dataRoamingTypeChanged(ServiceState oldSS, ServiceState newSS)616     private static boolean dataRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
617         return oldSS.getDataRoamingType() != newSS.getDataRoamingType();
618     }
619 
dataNetworkTypeChanged(ServiceState oldSS, ServiceState newSS)620     private static boolean dataNetworkTypeChanged(ServiceState oldSS, ServiceState newSS) {
621         return oldSS.getDataNetworkType() != newSS.getDataNetworkType();
622     }
623 
624     /**
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
notifyChangeForSubId(Context context, ServiceState oldSS, ServiceState newSS, int subId)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)
643                 || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)
644                 || dataNetworkTypeChanged(oldSS, newSS)) {
645             context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
646         }
647     }
648 
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      */
isPathPrefixMatch(Uri uriA, Uri uriB)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     }
673 
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      */
getContentValuesForServiceState(ServiceState state)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     }
690 
691     /**
692      * Check location permission with same policy as {@link TelephonyManager#getServiceState()}
693      * which enforces location permission check starting from Q.
694      */
hasLocationPermission()695     private boolean hasLocationPermission() {
696         LocationAccessPolicy.LocationPermissionResult locationPermissionResult =
697                 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)
705                                 .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
706                                 .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
707                                 .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
708                                 .build());
709         return locationPermissionResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
710     }
711 
712     // Return a copy of ServiceState with all sensitive info redacted.
713     @VisibleForTesting
getLocationRedactedServiceState(ServiceState serviceState)714     /* package */ static ServiceState getLocationRedactedServiceState(ServiceState serviceState) {
715         ServiceState ss =
716                 serviceState.createLocationInfoSanitizedCopy(true /*removeCoarseLocation*/);
717         return ss;
718     }
719 }
720