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