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