• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* //device/content/providers/telephony/TelephonyProvider.java
2 **
3 ** Copyright 2006, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 
18 package com.android.providers.telephony;
19 
20 import android.content.ContentProvider;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.SharedPreferences;
25 import android.content.UriMatcher;
26 import android.content.pm.PackageManager;
27 import android.content.res.Resources;
28 import android.content.res.XmlResourceParser;
29 import android.database.Cursor;
30 import android.database.SQLException;
31 import android.database.sqlite.SQLiteDatabase;
32 import android.database.sqlite.SQLiteException;
33 import android.database.sqlite.SQLiteOpenHelper;
34 import android.database.sqlite.SQLiteQueryBuilder;
35 import android.net.Uri;
36 import android.os.Binder;
37 import android.os.Environment;
38 import android.os.FileUtils;
39 import android.os.SystemProperties;
40 import android.os.UserHandle;
41 import android.telephony.ServiceState;
42 import android.telephony.SubscriptionInfo;
43 import android.telephony.SubscriptionManager;
44 import android.telephony.TelephonyManager;
45 import android.text.TextUtils;
46 import android.util.Log;
47 import android.util.Xml;
48 
49 import com.android.internal.util.XmlUtils;
50 
51 import org.xmlpull.v1.XmlPullParser;
52 import org.xmlpull.v1.XmlPullParserException;
53 
54 import java.io.File;
55 import java.io.FileNotFoundException;
56 import java.io.FileReader;
57 import java.io.IOException;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.List;
61 import java.util.Map;
62 
63 import static android.provider.Telephony.Carriers.*;
64 
65 public class TelephonyProvider extends ContentProvider
66 {
67     private static final String DATABASE_NAME = "telephony.db";
68     private static final boolean DBG = true;
69     private static final boolean VDBG = false; // STOPSHIP if true
70 
71     private static final int DATABASE_VERSION = 18 << 16;
72     private static final int URL_UNKNOWN = 0;
73     private static final int URL_TELEPHONY = 1;
74     private static final int URL_CURRENT = 2;
75     private static final int URL_ID = 3;
76     private static final int URL_RESTOREAPN = 4;
77     private static final int URL_PREFERAPN = 5;
78     private static final int URL_PREFERAPN_NO_UPDATE = 6;
79     private static final int URL_SIMINFO = 7;
80     private static final int URL_TELEPHONY_USING_SUBID = 8;
81     private static final int URL_CURRENT_USING_SUBID = 9;
82     private static final int URL_RESTOREAPN_USING_SUBID = 10;
83     private static final int URL_PREFERAPN_USING_SUBID = 11;
84     private static final int URL_PREFERAPN_NO_UPDATE_USING_SUBID = 12;
85     private static final int URL_SIMINFO_USING_SUBID = 13;
86     private static final int URL_UPDATE_DB = 14;
87 
88     private static final String TAG = "TelephonyProvider";
89     private static final String CARRIERS_TABLE = "carriers";
90     private static final String CARRIERS_TABLE_TMP = "carriers_tmp";
91     private static final String SIMINFO_TABLE = "siminfo";
92 
93     private static final String PREF_FILE_APN = "preferred-apn";
94     private static final String COLUMN_APN_ID = "apn_id";
95 
96     private static final String PREF_FILE_FULL_APN = "preferred-full-apn";
97     private static final String DB_VERSION_KEY = "version";
98 
99     private static final String BUILD_ID_FILE = "build-id";
100     private static final String RO_BUILD_ID = "ro_build_id";
101 
102     private static final String PREF_FILE = "telephonyprovider";
103     private static final String APN_CONF_CHECKSUM = "apn_conf_checksum";
104 
105     private static final String PARTNER_APNS_PATH = "etc/apns-conf.xml";
106     private static final String OEM_APNS_PATH = "telephony/apns-conf.xml";
107     private static final String OTA_UPDATED_APNS_PATH = "misc/apns-conf.xml";
108     private static final String OLD_APNS_PATH = "etc/old-apns-conf.xml";
109 
110     private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
111 
112     private static final ContentValues s_currentNullMap;
113     private static final ContentValues s_currentSetMap;
114 
115     private static final String IS_UNEDITED = EDITED + "=" + UNEDITED;
116     private static final String IS_EDITED = EDITED + "!=" + UNEDITED;
117     private static final String IS_USER_EDITED = EDITED + "=" + USER_EDITED;
118     private static final String IS_USER_DELETED = EDITED + "=" + USER_DELETED;
119     private static final String IS_NOT_USER_DELETED = EDITED + "!=" + USER_DELETED;
120     private static final String IS_USER_DELETED_BUT_PRESENT_IN_XML =
121             EDITED + "=" + USER_DELETED_BUT_PRESENT_IN_XML;
122     private static final String IS_NOT_USER_DELETED_BUT_PRESENT_IN_XML =
123             EDITED + "!=" + USER_DELETED_BUT_PRESENT_IN_XML;
124     private static final String IS_CARRIER_EDITED = EDITED + "=" + CARRIER_EDITED;
125     private static final String IS_CARRIER_DELETED = EDITED + "=" + CARRIER_DELETED;
126     private static final String IS_NOT_CARRIER_DELETED = EDITED + "!=" + CARRIER_DELETED;
127     private static final String IS_CARRIER_DELETED_BUT_PRESENT_IN_XML =
128             EDITED + "=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
129     private static final String IS_NOT_CARRIER_DELETED_BUT_PRESENT_IN_XML =
130             EDITED + "!=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
131 
132     private static final int INVALID_APN_ID = -1;
133     private static final List<String> CARRIERS_UNIQUE_FIELDS = new ArrayList<String>();
134 
135     static {
136         // Columns not included in UNIQUE constraint: name, current, edited, user, server, password,
137         // authtype, type, protocol, roaming_protocol, sub_id, modem_cognitive, max_conns,
138         // wait_time, max_conns_time, mtu, bearer_bitmask, user_visible
139         CARRIERS_UNIQUE_FIELDS.add(NUMERIC);
140         CARRIERS_UNIQUE_FIELDS.add(MCC);
141         CARRIERS_UNIQUE_FIELDS.add(MNC);
142         CARRIERS_UNIQUE_FIELDS.add(APN);
143         CARRIERS_UNIQUE_FIELDS.add(PROXY);
144         CARRIERS_UNIQUE_FIELDS.add(PORT);
145         CARRIERS_UNIQUE_FIELDS.add(MMSPROXY);
146         CARRIERS_UNIQUE_FIELDS.add(MMSPORT);
147         CARRIERS_UNIQUE_FIELDS.add(MMSC);
148         CARRIERS_UNIQUE_FIELDS.add(CARRIER_ENABLED);
149         CARRIERS_UNIQUE_FIELDS.add(BEARER);
150         CARRIERS_UNIQUE_FIELDS.add(MVNO_TYPE);
151         CARRIERS_UNIQUE_FIELDS.add(MVNO_MATCH_DATA);
152         CARRIERS_UNIQUE_FIELDS.add(PROFILE_ID);
153     }
154 
155     static {
156         s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
157         s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);
158         s_urlMatcher.addURI("telephony", "carriers/#", URL_ID);
159         s_urlMatcher.addURI("telephony", "carriers/restore", URL_RESTOREAPN);
160         s_urlMatcher.addURI("telephony", "carriers/preferapn", URL_PREFERAPN);
161         s_urlMatcher.addURI("telephony", "carriers/preferapn_no_update", URL_PREFERAPN_NO_UPDATE);
162 
163         s_urlMatcher.addURI("telephony", "siminfo", URL_SIMINFO);
164 
165         s_urlMatcher.addURI("telephony", "carriers/subId/*", URL_TELEPHONY_USING_SUBID);
166         s_urlMatcher.addURI("telephony", "carriers/current/subId/*", URL_CURRENT_USING_SUBID);
167         s_urlMatcher.addURI("telephony", "carriers/restore/subId/*", URL_RESTOREAPN_USING_SUBID);
168         s_urlMatcher.addURI("telephony", "carriers/preferapn/subId/*", URL_PREFERAPN_USING_SUBID);
169         s_urlMatcher.addURI("telephony", "carriers/preferapn_no_update/subId/*",
170                 URL_PREFERAPN_NO_UPDATE_USING_SUBID);
171 
172         s_urlMatcher.addURI("telephony", "carriers/update_db", URL_UPDATE_DB);
173 
174         s_currentNullMap = new ContentValues(1);
s_currentNullMap.put(CURRENT, "0")175         s_currentNullMap.put(CURRENT, "0");
176 
177         s_currentSetMap = new ContentValues(1);
s_currentSetMap.put(CURRENT, "1")178         s_currentSetMap.put(CURRENT, "1");
179     }
180 
181     private static class DatabaseHelper extends SQLiteOpenHelper {
182         // Context to access resources with
183         private Context mContext;
184 
185         /**
186          * DatabaseHelper helper class for loading apns into a database.
187          *
188          * @param context of the user.
189          */
DatabaseHelper(Context context)190         public DatabaseHelper(Context context) {
191             super(context, DATABASE_NAME, null, getVersion(context));
192             mContext = context;
193         }
194 
getVersion(Context context)195         private static int getVersion(Context context) {
196             if (VDBG) log("getVersion:+");
197             // Get the database version, combining a static schema version and the XML version
198             Resources r = context.getResources();
199             XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
200             try {
201                 XmlUtils.beginDocument(parser, "apns");
202                 int publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
203                 int version = DATABASE_VERSION | publicversion;
204                 if (VDBG) log("getVersion:- version=0x" + Integer.toHexString(version));
205                 return version;
206             } catch (Exception e) {
207                 loge("Can't get version of APN database" + e + " return version=" +
208                         Integer.toHexString(DATABASE_VERSION));
209                 return DATABASE_VERSION;
210             } finally {
211                 parser.close();
212             }
213         }
214 
215         @Override
onCreate(SQLiteDatabase db)216         public void onCreate(SQLiteDatabase db) {
217             if (DBG) log("dbh.onCreate:+ db=" + db);
218             createSimInfoTable(db);
219             createCarriersTable(db, CARRIERS_TABLE);
220             initDatabase(db);
221             if (DBG) log("dbh.onCreate:- db=" + db);
222         }
223 
224         @Override
onOpen(SQLiteDatabase db)225         public void onOpen(SQLiteDatabase db) {
226             if (VDBG) log("dbh.onOpen:+ db=" + db);
227             try {
228                 // Try to access the table and create it if "no such table"
229                 db.query(SIMINFO_TABLE, null, null, null, null, null, null);
230                 if (DBG) log("dbh.onOpen: ok, queried table=" + SIMINFO_TABLE);
231             } catch (SQLiteException e) {
232                 loge("Exception " + SIMINFO_TABLE + "e=" + e);
233                 if (e.getMessage().startsWith("no such table")) {
234                     createSimInfoTable(db);
235                 }
236             }
237             try {
238                 db.query(CARRIERS_TABLE, null, null, null, null, null, null);
239                 if (DBG) log("dbh.onOpen: ok, queried table=" + CARRIERS_TABLE);
240             } catch (SQLiteException e) {
241                 loge("Exception " + CARRIERS_TABLE + " e=" + e);
242                 if (e.getMessage().startsWith("no such table")) {
243                     createCarriersTable(db, CARRIERS_TABLE);
244                 }
245             }
246             if (VDBG) log("dbh.onOpen:- db=" + db);
247         }
248 
createSimInfoTable(SQLiteDatabase db)249         private void createSimInfoTable(SQLiteDatabase db) {
250             if (DBG) log("dbh.createSimInfoTable:+");
251             db.execSQL("CREATE TABLE " + SIMINFO_TABLE + "("
252                     + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
253                     + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
254                     + SubscriptionManager.SIM_SLOT_INDEX + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
255                     + SubscriptionManager.DISPLAY_NAME + " TEXT,"
256                     + SubscriptionManager.CARRIER_NAME + " TEXT,"
257                     + SubscriptionManager.NAME_SOURCE + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","
258                     + SubscriptionManager.COLOR + " INTEGER DEFAULT " + SubscriptionManager.COLOR_DEFAULT + ","
259                     + SubscriptionManager.NUMBER + " TEXT,"
260                     + SubscriptionManager.DISPLAY_NUMBER_FORMAT + " INTEGER NOT NULL DEFAULT " + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","
261                     + SubscriptionManager.DATA_ROAMING + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
262                     + SubscriptionManager.MCC + " INTEGER DEFAULT 0,"
263                     + SubscriptionManager.MNC + " INTEGER DEFAULT 0,"
264                     + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
265                     + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
266                     + SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1,"
267                     + SubscriptionManager.CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1,"
268                     + SubscriptionManager.CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4,"
269                     + SubscriptionManager.CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0,"
270                     + SubscriptionManager.CB_ALERT_VIBRATE + " INTEGER DEFAULT 1,"
271                     + SubscriptionManager.CB_ALERT_SPEECH + " INTEGER DEFAULT 1,"
272                     + SubscriptionManager.CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0,"
273                     + SubscriptionManager.CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1,"
274                     + SubscriptionManager.CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0,"
275                     + SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1"
276                     + ");");
277             if (DBG) log("dbh.createSimInfoTable:-");
278         }
279 
createCarriersTable(SQLiteDatabase db, String tableName)280         private void createCarriersTable(SQLiteDatabase db, String tableName) {
281             // Set up the database schema
282             if (DBG) log("dbh.createCarriersTable: " + tableName);
283             db.execSQL("CREATE TABLE " + tableName +
284                     "(_id INTEGER PRIMARY KEY," +
285                     NAME + " TEXT DEFAULT ''," +
286                     NUMERIC + " TEXT DEFAULT ''," +
287                     MCC + " TEXT DEFAULT ''," +
288                     MNC + " TEXT DEFAULT ''," +
289                     APN + " TEXT DEFAULT ''," +
290                     USER + " TEXT DEFAULT ''," +
291                     SERVER + " TEXT DEFAULT ''," +
292                     PASSWORD + " TEXT DEFAULT ''," +
293                     PROXY + " TEXT DEFAULT ''," +
294                     PORT + " TEXT DEFAULT ''," +
295                     MMSPROXY + " TEXT DEFAULT ''," +
296                     MMSPORT + " TEXT DEFAULT ''," +
297                     MMSC + " TEXT DEFAULT ''," +
298                     AUTH_TYPE + " INTEGER DEFAULT -1," +
299                     TYPE + " TEXT DEFAULT ''," +
300                     CURRENT + " INTEGER," +
301                     PROTOCOL + " TEXT DEFAULT 'IP'," +
302                     ROAMING_PROTOCOL + " TEXT DEFAULT 'IP'," +
303                     CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
304                     BEARER + " INTEGER DEFAULT 0," +
305                     BEARER_BITMASK + " INTEGER DEFAULT 0," +
306                     MVNO_TYPE + " TEXT DEFAULT ''," +
307                     MVNO_MATCH_DATA + " TEXT DEFAULT ''," +
308                     SUBSCRIPTION_ID + " INTEGER DEFAULT "
309                     + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +
310                     PROFILE_ID + " INTEGER DEFAULT 0," +
311                     MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," +
312                     MAX_CONNS + " INTEGER DEFAULT 0," +
313                     WAIT_TIME + " INTEGER DEFAULT 0," +
314                     MAX_CONNS_TIME + " INTEGER DEFAULT 0," +
315                     MTU + " INTEGER DEFAULT 0," +
316                     EDITED + " INTEGER DEFAULT " + UNEDITED + "," +
317                     USER_VISIBLE + " BOOLEAN DEFAULT 1," +
318                     // Uniqueness collisions are used to trigger merge code so if a field is listed
319                     // here it means we will accept both (user edited + new apn_conf definition)
320                     // Columns not included in UNIQUE constraint: name, current, edited,
321                     // user, server, password, authtype, type, protocol, roaming_protocol, sub_id,
322                     // modem_cognitive, max_conns, wait_time, max_conns_time, mtu, bearer_bitmask,
323                     // user_visible
324                     "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));");
325             if (DBG) log("dbh.createCarriersTable:-");
326         }
327 
getChecksum(File file)328         private long getChecksum(File file) {
329             long checksum = -1;
330             try {
331                 checksum = FileUtils.checksumCrc32(file);
332                 if (DBG) log("Checksum for " + file.getAbsolutePath() + " is " + checksum);
333             } catch (FileNotFoundException e) {
334                 loge("FileNotFoundException for " + file.getAbsolutePath() + ":" + e);
335             } catch (IOException e) {
336                 loge("IOException for " + file.getAbsolutePath() + ":" + e);
337             }
338             return checksum;
339         }
340 
getApnConfChecksum()341         private long getApnConfChecksum() {
342             SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
343             return sp.getLong(APN_CONF_CHECKSUM, -1);
344         }
345 
setApnConfChecksum(long checksum)346         private void setApnConfChecksum(long checksum) {
347             SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
348             SharedPreferences.Editor editor = sp.edit();
349             editor.putLong(APN_CONF_CHECKSUM, checksum);
350             editor.apply();
351         }
352 
getApnConfFile()353         private File getApnConfFile() {
354             // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
355             File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
356             File oemConfFile =  new File(Environment.getOemDirectory(), OEM_APNS_PATH);
357             File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);
358             confFile = getNewerFile(confFile, oemConfFile);
359             confFile = getNewerFile(confFile, updatedConfFile);
360             return confFile;
361         }
362 
363         /**
364          * This function computes checksum for the file to be read and compares it against the
365          * last read file. DB needs to be updated only if checksum has changed, or old checksum does
366          * not exist.
367          * @return true if DB should be updated with new conf file, false otherwise
368          */
apnDbUpdateNeeded()369         private boolean apnDbUpdateNeeded() {
370             File confFile = getApnConfFile();
371             long newChecksum = getChecksum(confFile);
372             long oldChecksum = getApnConfChecksum();
373             if (DBG) log("newChecksum: " + newChecksum);
374             if (DBG) log("oldChecksum: " + oldChecksum);
375             if (newChecksum == oldChecksum) {
376                 return false;
377             } else {
378                 return true;
379             }
380         }
381 
382         /**
383          *  This function adds APNs from xml file(s) to db. The db may or may not be empty to begin
384          *  with.
385          */
initDatabase(SQLiteDatabase db)386         private void initDatabase(SQLiteDatabase db) {
387             if (VDBG) log("dbh.initDatabase:+ db=" + db);
388             // Read internal APNS data
389             Resources r = mContext.getResources();
390             XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
391             int publicversion = -1;
392             try {
393                 XmlUtils.beginDocument(parser, "apns");
394                 publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
395                 loadApns(db, parser);
396             } catch (Exception e) {
397                 loge("Got exception while loading APN database." + e);
398             } finally {
399                 parser.close();
400             }
401 
402             // Read external APNS data (partner-provided)
403             XmlPullParser confparser = null;
404             File confFile = getApnConfFile();
405 
406             FileReader confreader = null;
407             if (DBG) log("confFile = " + confFile);
408             try {
409                 confreader = new FileReader(confFile);
410                 confparser = Xml.newPullParser();
411                 confparser.setInput(confreader);
412                 XmlUtils.beginDocument(confparser, "apns");
413 
414                 // Sanity check. Force internal version and confidential versions to agree
415                 int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
416                 if (publicversion != confversion) {
417                     log("initDatabase: throwing exception due to version mismatch");
418                     throw new IllegalStateException("Internal APNS file version doesn't match "
419                             + confFile.getAbsolutePath());
420                 }
421 
422                 loadApns(db, confparser);
423             } catch (FileNotFoundException e) {
424                 // It's ok if the file isn't found. It means there isn't a confidential file
425                 // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
426             } catch (Exception e) {
427                 loge("initDatabase: Exception while parsing '" + confFile.getAbsolutePath() + "'" +
428                         e);
429             } finally {
430                 // Get rid of user/carrier deleted entries that are not present in apn xml file.
431                 // Those entries have edited value USER_DELETED/CARRIER_DELETED.
432                 if (VDBG) {
433                     log("initDatabase: deleting USER_DELETED and replacing "
434                             + "DELETED_BUT_PRESENT_IN_XML with DELETED");
435                 }
436 
437                 // Delete USER_DELETED
438                 db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);
439 
440                 // Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
441                 ContentValues cv = new ContentValues();
442                 cv.put(EDITED, USER_DELETED);
443                 db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);
444 
445                 // Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
446                 cv = new ContentValues();
447                 cv.put(EDITED, CARRIER_DELETED);
448                 db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);
449 
450                 if (confreader != null) {
451                     try {
452                         confreader.close();
453                     } catch (IOException e) {
454                         // do nothing
455                     }
456                 }
457 
458                 // Update the stored checksum
459                 setApnConfChecksum(getChecksum(confFile));
460             }
461             if (VDBG) log("dbh.initDatabase:- db=" + db);
462 
463         }
464 
getNewerFile(File sysApnFile, File altApnFile)465         private File getNewerFile(File sysApnFile, File altApnFile) {
466             if (altApnFile.exists()) {
467                 // Alternate file exists. Use the newer one.
468                 long altFileTime = altApnFile.lastModified();
469                 long currFileTime = sysApnFile.lastModified();
470                 if (DBG) log("APNs Timestamp: altFileTime = " + altFileTime + " currFileTime = "
471                         + currFileTime);
472 
473                 // To get the latest version from OEM or System image
474                 if (altFileTime > currFileTime) {
475                     if (DBG) log("APNs Timestamp: Alternate image " + altApnFile.getPath() +
476                             " is greater than System image");
477                     return altApnFile;
478                 }
479             } else {
480                 // No Apn in alternate image, so load it from system image.
481                 if (DBG) log("No APNs in OEM image = " + altApnFile.getPath() +
482                         " Load APNs from system image");
483             }
484             return sysApnFile;
485         }
486 
487         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)488         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
489             if (DBG) {
490                 log("dbh.onUpgrade:+ db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
491             }
492 
493             if (oldVersion < (5 << 16 | 6)) {
494                 // 5 << 16 is the Database version and 6 in the xml version.
495 
496                 // This change adds a new authtype column to the database.
497                 // The auth type column can have 4 values: 0 (None), 1 (PAP), 2 (CHAP)
498                 // 3 (PAP or CHAP). To avoid breaking compatibility, with already working
499                 // APNs, the unset value (-1) will be used. If the value is -1.
500                 // the authentication will default to 0 (if no user / password) is specified
501                 // or to 3. Currently, there have been no reported problems with
502                 // pre-configured APNs and hence it is set to -1 for them. Similarly,
503                 // if the user, has added a new APN, we set the authentication type
504                 // to -1.
505 
506                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
507                         " ADD COLUMN authtype INTEGER DEFAULT -1;");
508 
509                 oldVersion = 5 << 16 | 6;
510             }
511             if (oldVersion < (6 << 16 | 6)) {
512                 // Add protcol fields to the APN. The XML file does not change.
513                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
514                         " ADD COLUMN protocol TEXT DEFAULT IP;");
515                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
516                         " ADD COLUMN roaming_protocol TEXT DEFAULT IP;");
517                 oldVersion = 6 << 16 | 6;
518             }
519             if (oldVersion < (7 << 16 | 6)) {
520                 // Add carrier_enabled, bearer fields to the APN. The XML file does not change.
521                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
522                         " ADD COLUMN carrier_enabled BOOLEAN DEFAULT 1;");
523                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
524                         " ADD COLUMN bearer INTEGER DEFAULT 0;");
525                 oldVersion = 7 << 16 | 6;
526             }
527             if (oldVersion < (8 << 16 | 6)) {
528                 // Add mvno_type, mvno_match_data fields to the APN.
529                 // The XML file does not change.
530                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
531                         " ADD COLUMN mvno_type TEXT DEFAULT '';");
532                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
533                         " ADD COLUMN mvno_match_data TEXT DEFAULT '';");
534                 oldVersion = 8 << 16 | 6;
535             }
536             if (oldVersion < (9 << 16 | 6)) {
537                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
538                         " ADD COLUMN sub_id INTEGER DEFAULT " +
539                         SubscriptionManager.INVALID_SUBSCRIPTION_ID + ";");
540                 oldVersion = 9 << 16 | 6;
541             }
542             if (oldVersion < (10 << 16 | 6)) {
543                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
544                         " ADD COLUMN profile_id INTEGER DEFAULT 0;");
545                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
546                         " ADD COLUMN modem_cognitive BOOLEAN DEFAULT 0;");
547                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
548                         " ADD COLUMN max_conns INTEGER DEFAULT 0;");
549                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
550                         " ADD COLUMN wait_time INTEGER DEFAULT 0;");
551                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
552                         " ADD COLUMN max_conns_time INTEGER DEFAULT 0;");
553                 oldVersion = 10 << 16 | 6;
554             }
555             if (oldVersion < (11 << 16 | 6)) {
556                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
557                         " ADD COLUMN mtu INTEGER DEFAULT 0;");
558                 oldVersion = 11 << 16 | 6;
559             }
560             if (oldVersion < (12 << 16 | 6)) {
561                 try {
562                     // Try to update the siminfo table. It might not be there.
563                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE +
564                             " ADD COLUMN " + SubscriptionManager.MCC + " INTEGER DEFAULT 0;");
565                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE +
566                             " ADD COLUMN " + SubscriptionManager.MNC + " INTEGER DEFAULT 0;");
567                 } catch (SQLiteException e) {
568                     if (DBG) {
569                         log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
570                                 " The table will get created in onOpen.");
571                     }
572                 }
573                 oldVersion = 12 << 16 | 6;
574             }
575             if (oldVersion < (13 << 16 | 6)) {
576                 try {
577                     // Try to update the siminfo table. It might not be there.
578                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN " +
579                             SubscriptionManager.CARRIER_NAME + " TEXT DEFAULT '';");
580                 } catch (SQLiteException e) {
581                     if (DBG) {
582                         log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
583                                 " The table will get created in onOpen.");
584                     }
585                 }
586                 oldVersion = 13 << 16 | 6;
587             }
588             if (oldVersion < (14 << 16 | 6)) {
589                 // Do nothing. This is to avoid recreating table twice. Table is anyway recreated
590                 // for next version and that takes care of updates for this version as well.
591                 // This version added a new column user_edited to carriers db.
592             }
593             if (oldVersion < (15 << 16 | 6)) {
594                 // Most devices should be upgrading from version 13. On upgrade new db will be
595                 // populated from the xml included in OTA but user and carrier edited/added entries
596                 // need to be preserved. This new version also adds new columns EDITED and
597                 // BEARER_BITMASK to the table. Upgrade steps from version 13 are:
598                 // 1. preserve user and carrier added/edited APNs (by comparing against
599                 // old-apns-conf.xml included in OTA) - done in preserveUserAndCarrierApns()
600                 // 2. add new columns EDITED and BEARER_BITMASK (create a new table for that) - done
601                 // in createCarriersTable()
602                 // 3. copy over preserved APNs from old table to new table - done in
603                 // copyPreservedApnsToNewTable()
604                 // The only exception if upgrading from version 14 is that EDITED field is already
605                 // present (but is called USER_EDITED)
606                 /*********************************************************************************
607                  * IMPORTANT NOTE: SINCE CARRIERS TABLE IS RECREATED HERE, IT WILL BE THE LATEST
608                  * VERSION AFTER THIS. AS A RESULT ANY SUBSEQUENT UPDATES TO THE TABLE WILL FAIL
609                  * (DUE TO COLUMN-ALREADY-EXISTS KIND OF EXCEPTION). ALL SUBSEQUENT UPDATES SHOULD
610                  * HANDLE THAT GRACEFULLY.
611                  *********************************************************************************/
612                 Cursor c;
613                 String[] proj = {"_id"};
614                 if (VDBG) {
615                     c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null);
616                     log("dbh.onUpgrade:- before upgrading total number of rows: " + c.getCount());
617                 }
618 
619                 // Compare db with old apns xml file so that any user or carrier edited/added
620                 // entries can be preserved across upgrade
621                 preserveUserAndCarrierApns(db);
622 
623                 c = db.query(CARRIERS_TABLE, null, null, null, null, null, null);
624 
625                 if (VDBG) {
626                     log("dbh.onUpgrade:- after preserveUserAndCarrierApns() total number of " +
627                             "rows: " + ((c == null) ? 0 : c.getCount()));
628                 }
629 
630                 createCarriersTable(db, CARRIERS_TABLE_TMP);
631 
632                 copyPreservedApnsToNewTable(db, c);
633                 c.close();
634 
635                 db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_TABLE);
636 
637                 db.execSQL("ALTER TABLE " + CARRIERS_TABLE_TMP + " rename to " + CARRIERS_TABLE +
638                         ";");
639 
640                 if (VDBG) {
641                     c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null);
642                     log("dbh.onUpgrade:- after upgrading total number of rows: " + c.getCount());
643                     c.close();
644                     c = db.query(CARRIERS_TABLE, proj, IS_UNEDITED, null, null, null, null);
645                     log("dbh.onUpgrade:- after upgrading total number of rows with " + IS_UNEDITED +
646                             ": " + c.getCount());
647                     c.close();
648                     c = db.query(CARRIERS_TABLE, proj, IS_EDITED, null, null, null, null);
649                     log("dbh.onUpgrade:- after upgrading total number of rows with " + IS_EDITED +
650                             ": " + c.getCount());
651                     c.close();
652                 }
653 
654                 oldVersion = 15 << 16 | 6;
655             }
656             if (oldVersion < (16 << 16 | 6)) {
657                 try {
658                     // Try to update the siminfo table. It might not be there.
659                     // These columns may already be present in which case execSQL will throw an
660                     // exception
661                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
662                             + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1;");
663                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
664                             + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1;");
665                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
666                             + SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1;");
667                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
668                             + SubscriptionManager.CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1;");
669                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
670                             + SubscriptionManager.CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4;");
671                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
672                             + SubscriptionManager.CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0;");
673                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
674                             + SubscriptionManager.CB_ALERT_VIBRATE + " INTEGER DEFAULT 1;");
675                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
676                             + SubscriptionManager.CB_ALERT_SPEECH + " INTEGER DEFAULT 1;");
677                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
678                             + SubscriptionManager.CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0;");
679                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
680                             + SubscriptionManager.CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1;");
681                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
682                             + SubscriptionManager.CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0;");
683                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
684                             + SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1;");
685                 } catch (SQLiteException e) {
686                     if (DBG) {
687                         log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
688                                 " The table will get created in onOpen.");
689                     }
690                 }
691                 oldVersion = 16 << 16 | 6;
692             }
693             if (oldVersion < (17 << 16 | 6)) {
694                 Cursor c = null;
695                 try {
696                     c = db.query(CARRIERS_TABLE, null, null, null, null, null, null,
697                             String.valueOf(1));
698                     if (c == null || c.getColumnIndex(USER_VISIBLE) == -1) {
699                         db.execSQL("ALTER TABLE " + CARRIERS_TABLE + " ADD COLUMN " +
700                                 USER_VISIBLE + " BOOLEAN DEFAULT 1;");
701                     } else {
702                         if (DBG) {
703                             log("onUpgrade skipping " + CARRIERS_TABLE + " upgrade.  Column " +
704                                     USER_VISIBLE + " already exists.");
705                         }
706                     }
707                 } finally {
708                     if (c != null) {
709                         c.close();
710                     }
711                 }
712                 oldVersion = 17 << 16 | 6;
713             }
714             if (oldVersion < (18 << 16 | 6)) {
715                 try {
716                     // Try to update the siminfo table. It might not be there.
717                     db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN " +
718                             SubscriptionManager.SIM_PROVISIONING_STATUS + " INTEGER DEFAULT " +
719                             SubscriptionManager.SIM_PROVISIONED + ";");
720                 } catch (SQLiteException e) {
721                     if (DBG) {
722                         log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
723                                 " The table will get created in onOpen.");
724                     }
725                 }
726                 oldVersion = 18 << 16 | 6;
727             }
728             if (DBG) {
729                 log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
730             }
731         }
732 
preserveUserAndCarrierApns(SQLiteDatabase db)733         private void preserveUserAndCarrierApns(SQLiteDatabase db) {
734             if (VDBG) log("preserveUserAndCarrierApns");
735             XmlPullParser confparser;
736             File confFile = new File(Environment.getRootDirectory(), OLD_APNS_PATH);
737             FileReader confreader = null;
738             try {
739                 confreader = new FileReader(confFile);
740                 confparser = Xml.newPullParser();
741                 confparser.setInput(confreader);
742                 XmlUtils.beginDocument(confparser, "apns");
743 
744                 deleteMatchingApns(db, confparser);
745             } catch (FileNotFoundException e) {
746                 // This function is called only when upgrading db to version 15. Details about the
747                 // upgrade are mentioned in onUpgrade(). This file missing means user/carrier added
748                 // APNs cannot be preserved. Log an error message so that OEMs know they need to
749                 // include old apns file for comparison.
750                 loge("PRESERVEUSERANDCARRIERAPNS: " + OLD_APNS_PATH +
751                         " NOT FOUND. IT IS NEEDED TO UPGRADE FROM OLDER VERSIONS OF APN " +
752                         "DB WHILE PRESERVING USER/CARRIER ADDED/EDITED ENTRIES.");
753             } catch (Exception e) {
754                 loge("preserveUserAndCarrierApns: Exception while parsing '" +
755                         confFile.getAbsolutePath() + "'" + e);
756             } finally {
757                 if (confreader != null) {
758                     try {
759                         confreader.close();
760                     } catch (IOException e) {
761                         // do nothing
762                     }
763                 }
764             }
765         }
766 
deleteMatchingApns(SQLiteDatabase db, XmlPullParser parser)767         private void deleteMatchingApns(SQLiteDatabase db, XmlPullParser parser) {
768             if (VDBG) log("deleteMatchingApns");
769             if (parser != null) {
770                 if (VDBG) log("deleteMatchingApns: parser != null");
771                 try {
772                     XmlUtils.nextElement(parser);
773                     while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
774                         ContentValues row = getRow(parser);
775                         if (row == null) {
776                             throw new XmlPullParserException("Expected 'apn' tag", parser, null);
777                         }
778                         deleteRow(db, row);
779                         XmlUtils.nextElement(parser);
780                     }
781                 } catch (XmlPullParserException e) {
782                     loge("deleteMatchingApns: Got XmlPullParserException while deleting apns." + e);
783                 } catch (IOException e) {
784                     loge("deleteMatchingApns: Got IOException while deleting apns." + e);
785                 } catch (SQLException e) {
786                     loge("deleteMatchingApns: Got SQLException while deleting apns." + e);
787                 }
788             }
789         }
790 
queryValFirst(String field)791         private String queryValFirst(String field) {
792             return field + "=?";
793         }
794 
queryVal(String field)795         private String queryVal(String field) {
796             return " and " + field + "=?";
797         }
798 
queryValOrNull(String field)799         private String queryValOrNull(String field) {
800             return " and (" + field + "=? or " + field + " is null)";
801         }
802 
queryVal2OrNull(String field)803         private String queryVal2OrNull(String field) {
804             return " and (" + field + "=? or " + field + "=? or " + field + " is null)";
805         }
806 
deleteRow(SQLiteDatabase db, ContentValues values)807         private void deleteRow(SQLiteDatabase db, ContentValues values) {
808             if (VDBG) log("deleteRow");
809             String where = queryValFirst(NUMERIC) +
810                     queryVal(MNC) +
811                     queryVal(MNC) +
812                     queryValOrNull(APN) +
813                     queryValOrNull(USER) +
814                     queryValOrNull(SERVER) +
815                     queryValOrNull(PASSWORD) +
816                     queryValOrNull(PROXY) +
817                     queryValOrNull(PORT) +
818                     queryValOrNull(MMSPROXY) +
819                     queryValOrNull(MMSPORT) +
820                     queryValOrNull(MMSC) +
821                     queryValOrNull(AUTH_TYPE) +
822                     queryValOrNull(TYPE) +
823                     queryValOrNull(PROTOCOL) +
824                     queryValOrNull(ROAMING_PROTOCOL) +
825                     queryVal2OrNull(CARRIER_ENABLED) +
826                     queryValOrNull(BEARER) +
827                     queryValOrNull(MVNO_TYPE) +
828                     queryValOrNull(MVNO_MATCH_DATA) +
829                     queryValOrNull(PROFILE_ID) +
830                     queryVal2OrNull(MODEM_COGNITIVE) +
831                     queryValOrNull(MAX_CONNS) +
832                     queryValOrNull(WAIT_TIME) +
833                     queryValOrNull(MAX_CONNS_TIME) +
834                     queryValOrNull(MTU);
835             String[] whereArgs = new String[29];
836             int i = 0;
837             whereArgs[i++] = values.getAsString(NUMERIC);
838             whereArgs[i++] = values.getAsString(MCC);
839             whereArgs[i++] = values.getAsString(MNC);
840             whereArgs[i++] = values.getAsString(NAME);
841             whereArgs[i++] = values.containsKey(APN) ?
842                     values.getAsString(APN) : "";
843             whereArgs[i++] = values.containsKey(USER) ?
844                     values.getAsString(USER) : "";
845             whereArgs[i++] = values.containsKey(SERVER) ?
846                     values.getAsString(SERVER) : "";
847             whereArgs[i++] = values.containsKey(PASSWORD) ?
848                     values.getAsString(PASSWORD) : "";
849             whereArgs[i++] = values.containsKey(PROXY) ?
850                     values.getAsString(PROXY) : "";
851             whereArgs[i++] = values.containsKey(PORT) ?
852                     values.getAsString(PORT) : "";
853             whereArgs[i++] = values.containsKey(MMSPROXY) ?
854                     values.getAsString(MMSPROXY) : "";
855             whereArgs[i++] = values.containsKey(MMSPORT) ?
856                     values.getAsString(MMSPORT) : "";
857             whereArgs[i++] = values.containsKey(MMSC) ?
858                     values.getAsString(MMSC) : "";
859             whereArgs[i++] = values.containsKey(AUTH_TYPE) ?
860                     values.getAsString(AUTH_TYPE) : "-1";
861             whereArgs[i++] = values.containsKey(TYPE) ?
862                     values.getAsString(TYPE) : "";
863             whereArgs[i++] = values.containsKey(PROTOCOL) ?
864                     values.getAsString(PROTOCOL) : "IP";
865             whereArgs[i++] = values.containsKey(ROAMING_PROTOCOL) ?
866                     values.getAsString(ROAMING_PROTOCOL) : "IP";
867 
868             if (values.containsKey(CARRIER_ENABLED) &&
869                     (values.getAsString(CARRIER_ENABLED).
870                             equalsIgnoreCase("false") ||
871                             values.getAsString(CARRIER_ENABLED).equals("0"))) {
872                 whereArgs[i++] = "false";
873                 whereArgs[i++] = "0";
874             } else {
875                 whereArgs[i++] = "true";
876                 whereArgs[i++] = "1";
877             }
878 
879             whereArgs[i++] = values.containsKey(BEARER) ?
880                     values.getAsString(BEARER) : "0";
881             whereArgs[i++] = values.containsKey(MVNO_TYPE) ?
882                     values.getAsString(MVNO_TYPE) : "";
883             whereArgs[i++] = values.containsKey(MVNO_MATCH_DATA) ?
884                     values.getAsString(MVNO_MATCH_DATA) : "";
885             whereArgs[i++] = values.containsKey(PROFILE_ID) ?
886                     values.getAsString(PROFILE_ID) : "0";
887 
888             if (values.containsKey(MODEM_COGNITIVE) &&
889                     (values.getAsString(MODEM_COGNITIVE).
890                             equalsIgnoreCase("true") ||
891                             values.getAsString(MODEM_COGNITIVE).equals("1"))) {
892                 whereArgs[i++] = "true";
893                 whereArgs[i++] = "1";
894             } else {
895                 whereArgs[i++] = "false";
896                 whereArgs[i++] = "0";
897             }
898 
899             whereArgs[i++] = values.containsKey(MAX_CONNS) ?
900                     values.getAsString(MAX_CONNS) : "0";
901             whereArgs[i++] = values.containsKey(WAIT_TIME) ?
902                     values.getAsString(WAIT_TIME) : "0";
903             whereArgs[i++] = values.containsKey(MAX_CONNS_TIME) ?
904                     values.getAsString(MAX_CONNS_TIME) : "0";
905             whereArgs[i++] = values.containsKey(MTU) ?
906                     values.getAsString(MTU) : "0";
907 
908             if (VDBG) {
909                 log("deleteRow: where: " + where);
910 
911                 StringBuilder builder = new StringBuilder();
912                 for (String s : whereArgs) {
913                     builder.append(s + ", ");
914                 }
915 
916                 log("deleteRow: whereArgs: " + builder.toString());
917             }
918             db.delete(CARRIERS_TABLE, where, whereArgs);
919         }
920 
copyPreservedApnsToNewTable(SQLiteDatabase db, Cursor c)921         private void copyPreservedApnsToNewTable(SQLiteDatabase db, Cursor c) {
922             // Move entries from CARRIERS_TABLE to CARRIERS_TABLE_TMP
923             if (c != null) {
924                 String[] persistApnsForPlmns = mContext.getResources().getStringArray(
925                         R.array.persist_apns_for_plmn);
926                 while (c.moveToNext()) {
927                     ContentValues cv = new ContentValues();
928                     String val;
929 
930                     // Include only non-null values in cv so that null values can be replaced
931                     // with default if there's a default value for the field
932 
933                     // String vals
934                     getStringValueFromCursor(cv, c, NAME);
935                     getStringValueFromCursor(cv, c, NUMERIC);
936                     getStringValueFromCursor(cv, c, MCC);
937                     getStringValueFromCursor(cv, c, MNC);
938                     getStringValueFromCursor(cv, c, APN);
939                     getStringValueFromCursor(cv, c, USER);
940                     getStringValueFromCursor(cv, c, SERVER);
941                     getStringValueFromCursor(cv, c, PASSWORD);
942                     getStringValueFromCursor(cv, c, PROXY);
943                     getStringValueFromCursor(cv, c, PORT);
944                     getStringValueFromCursor(cv, c, MMSPROXY);
945                     getStringValueFromCursor(cv, c, MMSPORT);
946                     getStringValueFromCursor(cv, c, MMSC);
947                     getStringValueFromCursor(cv, c, TYPE);
948                     getStringValueFromCursor(cv, c, PROTOCOL);
949                     getStringValueFromCursor(cv, c, ROAMING_PROTOCOL);
950                     getStringValueFromCursor(cv, c, MVNO_TYPE);
951                     getStringValueFromCursor(cv, c, MVNO_MATCH_DATA);
952 
953                     // bool/int vals
954                     getIntValueFromCursor(cv, c, AUTH_TYPE);
955                     getIntValueFromCursor(cv, c, CURRENT);
956                     getIntValueFromCursor(cv, c, CARRIER_ENABLED);
957                     getIntValueFromCursor(cv, c, BEARER);
958                     getIntValueFromCursor(cv, c, SUBSCRIPTION_ID);
959                     getIntValueFromCursor(cv, c, PROFILE_ID);
960                     getIntValueFromCursor(cv, c, MODEM_COGNITIVE);
961                     getIntValueFromCursor(cv, c, MAX_CONNS);
962                     getIntValueFromCursor(cv, c, WAIT_TIME);
963                     getIntValueFromCursor(cv, c, MAX_CONNS_TIME);
964                     getIntValueFromCursor(cv, c, MTU);
965 
966                     // Change bearer to a bitmask
967                     String bearerStr = c.getString(c.getColumnIndex(BEARER));
968                     if (!TextUtils.isEmpty(bearerStr)) {
969                         int bearer_bitmask = ServiceState.getBitmaskForTech(
970                                 Integer.parseInt(bearerStr));
971                         cv.put(BEARER_BITMASK, bearer_bitmask);
972                     }
973 
974                     int userEditedColumnIdx = c.getColumnIndex("user_edited");
975                     if (userEditedColumnIdx != -1) {
976                         String user_edited = c.getString(userEditedColumnIdx);
977                         if (!TextUtils.isEmpty(user_edited)) {
978                             cv.put(EDITED, new Integer(user_edited));
979                         }
980                     } else {
981                         cv.put(EDITED, USER_EDITED);
982                     }
983 
984                     // New EDITED column. Default value (UNEDITED) will
985                     // be used for all rows except for non-mvno entries for plmns indicated
986                     // by resource: those will be set to CARRIER_EDITED to preserve
987                     // their current values
988                     val = c.getString(c.getColumnIndex(NUMERIC));
989                     for (String s : persistApnsForPlmns) {
990                         if (!TextUtils.isEmpty(val) && val.equals(s) &&
991                                 (!cv.containsKey(MVNO_TYPE) ||
992                                         TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) {
993                             if (userEditedColumnIdx == -1) {
994                                 cv.put(EDITED, CARRIER_EDITED);
995                             } else { // if (oldVersion == 14) -- if db had user_edited column
996                                 if (cv.getAsInteger(EDITED) == USER_EDITED) {
997                                     cv.put(EDITED, CARRIER_EDITED);
998                                 }
999                             }
1000 
1001                             break;
1002                         }
1003                     }
1004 
1005                     try {
1006                         db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
1007                                 SQLiteDatabase.CONFLICT_ABORT);
1008                         if (VDBG) {
1009                             log("dbh.copyPreservedApnsToNewTable: db.insert returned >= 0; " +
1010                                     "insert successful for cv " + cv);
1011                         }
1012                     } catch (SQLException e) {
1013                         if (VDBG)
1014                             log("dbh.copyPreservedApnsToNewTable insertWithOnConflict exception " +
1015                                     e + " for cv " + cv);
1016                         // Insertion failed which could be due to a conflict. Check if that is
1017                         // the case and merge the entries
1018                         Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
1019                                 CARRIERS_TABLE_TMP, cv);
1020                         if (oldRow != null) {
1021                             ContentValues mergedValues = new ContentValues();
1022                             mergeFieldsAndUpdateDb(db, CARRIERS_TABLE_TMP, oldRow, cv,
1023                                     mergedValues, true, mContext);
1024                             oldRow.close();
1025                         }
1026                     }
1027                 }
1028             }
1029         }
1030 
getStringValueFromCursor(ContentValues cv, Cursor c, String key)1031         private void getStringValueFromCursor(ContentValues cv, Cursor c, String key) {
1032             String fromCursor = c.getString(c.getColumnIndex(key));
1033             if (!TextUtils.isEmpty(fromCursor)) {
1034                 cv.put(key, fromCursor);
1035             }
1036         }
1037 
getIntValueFromCursor(ContentValues cv, Cursor c, String key)1038         private void getIntValueFromCursor(ContentValues cv, Cursor c, String key) {
1039             String fromCursor = c.getString(c.getColumnIndex(key));
1040             if (!TextUtils.isEmpty(fromCursor)) {
1041                 try {
1042                     cv.put(key, new Integer(fromCursor));
1043                 } catch (NumberFormatException nfe) {
1044                     // do nothing
1045                 }
1046             }
1047         }
1048 
1049         /**
1050          * Gets the next row of apn values.
1051          *
1052          * @param parser the parser
1053          * @return the row or null if it's not an apn
1054          */
getRow(XmlPullParser parser)1055         private ContentValues getRow(XmlPullParser parser) {
1056             if (!"apn".equals(parser.getName())) {
1057                 return null;
1058             }
1059 
1060             ContentValues map = new ContentValues();
1061 
1062             String mcc = parser.getAttributeValue(null, "mcc");
1063             String mnc = parser.getAttributeValue(null, "mnc");
1064             String numeric = mcc + mnc;
1065 
1066             map.put(NUMERIC, numeric);
1067             map.put(MCC, mcc);
1068             map.put(MNC, mnc);
1069             map.put(NAME, parser.getAttributeValue(null, "carrier"));
1070 
1071             // do not add NULL to the map so that default values can be inserted in db
1072             addStringAttribute(parser, "apn", map, APN);
1073             addStringAttribute(parser, "user", map, USER);
1074             addStringAttribute(parser, "server", map, SERVER);
1075             addStringAttribute(parser, "password", map, PASSWORD);
1076             addStringAttribute(parser, "proxy", map, PROXY);
1077             addStringAttribute(parser, "port", map, PORT);
1078             addStringAttribute(parser, "mmsproxy", map, MMSPROXY);
1079             addStringAttribute(parser, "mmsport", map, MMSPORT);
1080             addStringAttribute(parser, "mmsc", map, MMSC);
1081             addStringAttribute(parser, "type", map, TYPE);
1082             addStringAttribute(parser, "protocol", map, PROTOCOL);
1083             addStringAttribute(parser, "roaming_protocol", map, ROAMING_PROTOCOL);
1084 
1085             addIntAttribute(parser, "authtype", map, AUTH_TYPE);
1086             addIntAttribute(parser, "bearer", map, BEARER);
1087             addIntAttribute(parser, "profile_id", map, PROFILE_ID);
1088             addIntAttribute(parser, "max_conns", map, MAX_CONNS);
1089             addIntAttribute(parser, "wait_time", map, WAIT_TIME);
1090             addIntAttribute(parser, "max_conns_time", map, MAX_CONNS_TIME);
1091             addIntAttribute(parser, "mtu", map, MTU);
1092 
1093 
1094             addBoolAttribute(parser, "carrier_enabled", map, CARRIER_ENABLED);
1095             addBoolAttribute(parser, "modem_cognitive", map, MODEM_COGNITIVE);
1096             addBoolAttribute(parser, "user_visible", map, USER_VISIBLE);
1097 
1098             int bearerBitmask = 0;
1099             String bearerList = parser.getAttributeValue(null, "bearer_bitmask");
1100             if (bearerList != null) {
1101                 bearerBitmask = ServiceState.getBitmaskFromString(bearerList);
1102             }
1103             map.put(BEARER_BITMASK, bearerBitmask);
1104 
1105             String mvno_type = parser.getAttributeValue(null, "mvno_type");
1106             if (mvno_type != null) {
1107                 String mvno_match_data = parser.getAttributeValue(null, "mvno_match_data");
1108                 if (mvno_match_data != null) {
1109                     map.put(MVNO_TYPE, mvno_type);
1110                     map.put(MVNO_MATCH_DATA, mvno_match_data);
1111                 }
1112             }
1113 
1114             return map;
1115         }
1116 
addStringAttribute(XmlPullParser parser, String att, ContentValues map, String key)1117         private void addStringAttribute(XmlPullParser parser, String att,
1118                                         ContentValues map, String key) {
1119             String val = parser.getAttributeValue(null, att);
1120             if (val != null) {
1121                 map.put(key, val);
1122             }
1123         }
1124 
addIntAttribute(XmlPullParser parser, String att, ContentValues map, String key)1125         private void addIntAttribute(XmlPullParser parser, String att,
1126                                      ContentValues map, String key) {
1127             String val = parser.getAttributeValue(null, att);
1128             if (val != null) {
1129                 map.put(key, Integer.parseInt(val));
1130             }
1131         }
1132 
addBoolAttribute(XmlPullParser parser, String att, ContentValues map, String key)1133         private void addBoolAttribute(XmlPullParser parser, String att,
1134                                       ContentValues map, String key) {
1135             String val = parser.getAttributeValue(null, att);
1136             if (val != null) {
1137                 map.put(key, Boolean.parseBoolean(val));
1138             }
1139         }
1140 
1141         /*
1142          * Loads apns from xml file into the database
1143          *
1144          * @param db the sqlite database to write to
1145          * @param parser the xml parser
1146          *
1147          */
loadApns(SQLiteDatabase db, XmlPullParser parser)1148         private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
1149             if (parser != null) {
1150                 try {
1151                     db.beginTransaction();
1152                     XmlUtils.nextElement(parser);
1153                     while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
1154                         ContentValues row = getRow(parser);
1155                         if (row == null) {
1156                             throw new XmlPullParserException("Expected 'apn' tag", parser, null);
1157                         }
1158                         insertAddingDefaults(db, row);
1159                         XmlUtils.nextElement(parser);
1160                     }
1161                     db.setTransactionSuccessful();
1162                 } catch (XmlPullParserException e) {
1163                     loge("Got XmlPullParserException while loading apns." + e);
1164                 } catch (IOException e) {
1165                     loge("Got IOException while loading apns." + e);
1166                 } catch (SQLException e) {
1167                     loge("Got SQLException while loading apns." + e);
1168                 } finally {
1169                     db.endTransaction();
1170                 }
1171             }
1172         }
1173 
setDefaultValue(ContentValues values)1174         static public ContentValues setDefaultValue(ContentValues values) {
1175             if (!values.containsKey(SUBSCRIPTION_ID)) {
1176                 int subId = SubscriptionManager.getDefaultSubscriptionId();
1177                 values.put(SUBSCRIPTION_ID, subId);
1178             }
1179 
1180             return values;
1181         }
1182 
insertAddingDefaults(SQLiteDatabase db, ContentValues row)1183         private void insertAddingDefaults(SQLiteDatabase db, ContentValues row) {
1184             row = setDefaultValue(row);
1185             try {
1186                 db.insertWithOnConflict(CARRIERS_TABLE, null, row, SQLiteDatabase.CONFLICT_ABORT);
1187                 if (VDBG) log("dbh.insertAddingDefaults: db.insert returned >= 0; insert " +
1188                         "successful for cv " + row);
1189             } catch (SQLException e) {
1190                 if (VDBG) log("dbh.insertAddingDefaults: exception " + e);
1191                 // Insertion failed which could be due to a conflict. Check if that is the case and
1192                 // update edited field accordingly.
1193                 // Search for the exact same entry and update edited field.
1194                 // If it is USER_EDITED/CARRIER_EDITED change it to UNEDITED,
1195                 // and if USER/CARRIER_DELETED change it to USER/CARRIER_DELETED_BUT_PRESENT_IN_XML.
1196                 Cursor oldRow = selectConflictingRow(db, CARRIERS_TABLE, row);
1197                 if (oldRow != null) {
1198                     // Update the row
1199                     ContentValues mergedValues = new ContentValues();
1200                     int edited = oldRow.getInt(oldRow.getColumnIndex(EDITED));
1201                     int old_edited = edited;
1202                     if (edited != UNEDITED) {
1203                         if (edited == USER_DELETED) {
1204                             // USER_DELETED_BUT_PRESENT_IN_XML indicates entry has been deleted
1205                             // by user but present in apn xml file.
1206                             edited = USER_DELETED_BUT_PRESENT_IN_XML;
1207                         } else if (edited == CARRIER_DELETED) {
1208                             // CARRIER_DELETED_BUT_PRESENT_IN_XML indicates entry has been deleted
1209                             // by user but present in apn xml file.
1210                             edited = CARRIER_DELETED_BUT_PRESENT_IN_XML;
1211                         }
1212                         mergedValues.put(EDITED, edited);
1213                     }
1214 
1215                     mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, row, mergedValues, false,
1216                             mContext);
1217 
1218                     if (VDBG) log("dbh.insertAddingDefaults: old edited = " + old_edited
1219                             + " new edited = " + edited);
1220 
1221                     oldRow.close();
1222                 }
1223             }
1224         }
1225 
mergeFieldsAndUpdateDb(SQLiteDatabase db, String table, Cursor oldRow, ContentValues newRow, ContentValues mergedValues, boolean onUpgrade, Context context)1226         public static void mergeFieldsAndUpdateDb(SQLiteDatabase db, String table, Cursor oldRow,
1227                                                   ContentValues newRow, ContentValues mergedValues,
1228                                                   boolean onUpgrade, Context context) {
1229             if (newRow.containsKey(TYPE)) {
1230                 // Merge the types
1231                 String oldType = oldRow.getString(oldRow.getColumnIndex(TYPE));
1232                 String newType = newRow.getAsString(TYPE);
1233 
1234                 if (!oldType.equalsIgnoreCase(newType)) {
1235                     if (oldType.equals("") || newType.equals("")) {
1236                         newRow.put(TYPE, "");
1237                     } else {
1238                         String[] oldTypes = oldType.toLowerCase().split(",");
1239                         String[] newTypes = newType.toLowerCase().split(",");
1240 
1241                         if (VDBG) {
1242                             log("mergeFieldsAndUpdateDb: Calling separateRowsNeeded() oldType=" +
1243                                     oldType + " old bearer=" + oldRow.getInt(oldRow.getColumnIndex(
1244                                     BEARER_BITMASK)) +
1245                                     " old profile_id=" + oldRow.getInt(oldRow.getColumnIndex(
1246                                     PROFILE_ID)) +
1247                                     " newRow " + newRow);
1248                         }
1249 
1250                         // If separate rows are needed, do not need to merge any further
1251                         if (separateRowsNeeded(db, table, oldRow, newRow, context, oldTypes,
1252                                 newTypes)) {
1253                             if (VDBG) log("mergeFieldsAndUpdateDb: separateRowsNeeded() returned " +
1254                                     "true");
1255                             return;
1256                         }
1257 
1258                         // Merge the 2 types
1259                         ArrayList<String> mergedTypes = new ArrayList<String>();
1260                         mergedTypes.addAll(Arrays.asList(oldTypes));
1261                         for (String s : newTypes) {
1262                             if (!mergedTypes.contains(s.trim())) {
1263                                 mergedTypes.add(s);
1264                             }
1265                         }
1266                         StringBuilder mergedType = new StringBuilder();
1267                         for (int i = 0; i < mergedTypes.size(); i++) {
1268                             mergedType.append((i == 0 ? "" : ",") + mergedTypes.get(i));
1269                         }
1270                         newRow.put(TYPE, mergedType.toString());
1271                     }
1272                 }
1273                 mergedValues.put(TYPE, newRow.getAsString(
1274                         TYPE));
1275             }
1276 
1277             if (newRow.containsKey(BEARER_BITMASK)) {
1278                 int oldBearer = oldRow.getInt(oldRow.getColumnIndex(BEARER_BITMASK));
1279                 int newBearer = newRow.getAsInteger(BEARER_BITMASK);
1280                 if (oldBearer != newBearer) {
1281                     if (oldBearer == 0 || newBearer == 0) {
1282                         newRow.put(BEARER_BITMASK, 0);
1283                     } else {
1284                         newRow.put(BEARER_BITMASK, (oldBearer | newBearer));
1285                     }
1286                 }
1287                 mergedValues.put(BEARER_BITMASK, newRow.getAsInteger(BEARER_BITMASK));
1288             }
1289 
1290             if (!onUpgrade) {
1291                 mergedValues.putAll(newRow);
1292             }
1293 
1294             if (mergedValues.size() > 0) {
1295                 db.update(table, mergedValues, "_id=" + oldRow.getInt(oldRow.getColumnIndex("_id")),
1296                         null);
1297             }
1298         }
1299 
separateRowsNeeded(SQLiteDatabase db, String table, Cursor oldRow, ContentValues newRow, Context context, String[] oldTypes, String[] newTypes)1300         private static boolean separateRowsNeeded(SQLiteDatabase db, String table, Cursor oldRow,
1301                                                   ContentValues newRow, Context context,
1302                                                   String[] oldTypes, String[] newTypes) {
1303             // If this APN falls under persist_apns_for_plmn, and the
1304             // only difference between old type and new type is that one has dun, and
1305             // the APNs have profile_id 0 or not set, then set the profile_id to 1 for
1306             // the dun APN/remove dun from type. This will ensure both oldRow and newRow exist
1307             // separately in db.
1308 
1309             boolean match = false;
1310 
1311             // Check if APN falls under persist_apns_for_plmn
1312             String[] persistApnsForPlmns = context.getResources().getStringArray(
1313                     R.array.persist_apns_for_plmn);
1314             for (String s : persistApnsForPlmns) {
1315                 if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
1316                     match = true;
1317                     break;
1318                 }
1319             }
1320 
1321             if (!match) return false;
1322 
1323             // APN falls under persist_apns_for_plmn
1324             // Check if only difference between old type and new type is that
1325             // one has dun
1326             ArrayList<String> oldTypesAl = new ArrayList<String>(Arrays.asList(oldTypes));
1327             ArrayList<String> newTypesAl = new ArrayList<String>(Arrays.asList(newTypes));
1328             ArrayList<String> listWithDun = null;
1329             ArrayList<String> listWithoutDun = null;
1330             boolean dunInOld = false;
1331             if (oldTypesAl.size() == newTypesAl.size() + 1) {
1332                 listWithDun = oldTypesAl;
1333                 listWithoutDun = newTypesAl;
1334                 dunInOld = true;
1335             } else if (oldTypesAl.size() + 1 == newTypesAl.size()) {
1336                 listWithDun = newTypesAl;
1337                 listWithoutDun = oldTypesAl;
1338             } else {
1339                 return false;
1340             }
1341 
1342             if (listWithDun.contains("dun") && !listWithoutDun.contains("dun")) {
1343                 listWithoutDun.add("dun");
1344                 if (!listWithDun.containsAll(listWithoutDun)) {
1345                     return false;
1346                 }
1347 
1348                 // Only difference between old type and new type is that
1349                 // one has dun
1350                 // Check if profile_id is 0/not set
1351                 if (oldRow.getInt(oldRow.getColumnIndex(PROFILE_ID)) == 0) {
1352                     if (dunInOld) {
1353                         // Update oldRow to remove dun from its type field
1354                         ContentValues updateOldRow = new ContentValues();
1355                         StringBuilder sb = new StringBuilder();
1356                         boolean first = true;
1357                         for (String s : listWithDun) {
1358                             if (!s.equalsIgnoreCase("dun")) {
1359                                 sb.append(first ? s : "," + s);
1360                                 first = false;
1361                             }
1362                         }
1363                         String updatedType = sb.toString();
1364                         if (VDBG) {
1365                             log("separateRowsNeeded: updating type in oldRow to " + updatedType);
1366                         }
1367                         updateOldRow.put(TYPE, updatedType);
1368                         db.update(table, updateOldRow,
1369                                 "_id=" + oldRow.getInt(oldRow.getColumnIndex("_id")), null);
1370                         return true;
1371                     } else {
1372                         if (VDBG) log("separateRowsNeeded: adding profile id 1 to newRow");
1373                         // Update newRow to set profile_id to 1
1374                         newRow.put(PROFILE_ID, new Integer(1));
1375                     }
1376                 } else {
1377                     return false;
1378                 }
1379 
1380                 // If match was found, both oldRow and newRow need to exist
1381                 // separately in db. Add newRow to db.
1382                 try {
1383                     db.insertWithOnConflict(table, null, newRow, SQLiteDatabase.CONFLICT_REPLACE);
1384                     if (VDBG) log("separateRowsNeeded: added newRow with profile id 1 to db");
1385                     return true;
1386                 } catch (SQLException e) {
1387                     loge("Exception on trying to add new row after updating profile_id");
1388                 }
1389             }
1390 
1391             return false;
1392         }
1393 
selectConflictingRow(SQLiteDatabase db, String table, ContentValues row)1394         public static Cursor selectConflictingRow(SQLiteDatabase db, String table,
1395                                                   ContentValues row) {
1396             // Conflict is possible only when numeric, mcc, mnc (fields without any default value)
1397             // are set in the new row
1398             if (!row.containsKey(NUMERIC) || !row.containsKey(MCC) || !row.containsKey(MNC)) {
1399                 loge("dbh.selectConflictingRow: called for non-conflicting row: " + row);
1400                 return null;
1401             }
1402 
1403             String[] columns = { "_id",
1404                     TYPE,
1405                     EDITED,
1406                     BEARER_BITMASK,
1407                     PROFILE_ID };
1408             String selection = TextUtils.join("=? AND ", CARRIERS_UNIQUE_FIELDS) + "=?";
1409             int i = 0;
1410             String[] selectionArgs = new String[14];
1411             selectionArgs[i++] = row.getAsString(NUMERIC);
1412             selectionArgs[i++] = row.getAsString(MCC);
1413             selectionArgs[i++] = row.getAsString(MNC);
1414             selectionArgs[i++] = row.containsKey(APN) ? row.getAsString(APN) : "";
1415             selectionArgs[i++] = row.containsKey(PROXY) ? row.getAsString(PROXY) : "";
1416             selectionArgs[i++] = row.containsKey(PORT) ? row.getAsString(PORT) : "";
1417             selectionArgs[i++] = row.containsKey(MMSPROXY) ? row.getAsString(MMSPROXY) : "";
1418             selectionArgs[i++] = row.containsKey(MMSPORT) ? row.getAsString(MMSPORT) : "";
1419             selectionArgs[i++] = row.containsKey(MMSC) ? row.getAsString(MMSC) : "";
1420             selectionArgs[i++] = row.containsKey(CARRIER_ENABLED) &&
1421                     (row.getAsString(CARRIER_ENABLED).equals("0") ||
1422                             row.getAsString(CARRIER_ENABLED).equals("false")) ?
1423                     "0" : "1";
1424             selectionArgs[i++] = row.containsKey(BEARER) ? row.getAsString(BEARER) : "0";
1425             selectionArgs[i++] = row.containsKey(MVNO_TYPE) ? row.getAsString(MVNO_TYPE) : "";
1426             selectionArgs[i++] = row.containsKey(MVNO_MATCH_DATA) ?
1427                     row.getAsString(MVNO_MATCH_DATA) : "";
1428             selectionArgs[i++] = row.containsKey(PROFILE_ID) ? row.getAsString(PROFILE_ID) : "0";
1429 
1430             Cursor c = db.query(table, columns, selection, selectionArgs, null, null, null);
1431 
1432             if (c != null) {
1433                 if (c.getCount() == 1) {
1434                     if (VDBG) log("dbh.selectConflictingRow: " + c.getCount() + " conflicting " +
1435                             "row found");
1436                     if (c.moveToFirst()) {
1437                         return c;
1438                     } else {
1439                         loge("dbh.selectConflictingRow: moveToFirst() failed");
1440                     }
1441                 } else {
1442                     loge("dbh.selectConflictingRow: Expected 1 but found " + c.getCount() +
1443                             " matching rows found for cv " + row);
1444                 }
1445                 c.close();
1446             } else {
1447                 loge("dbh.selectConflictingRow: Error - c is null; no matching row found for " +
1448                         "cv " + row);
1449             }
1450 
1451             return null;
1452         }
1453     }
1454 
1455     @Override
onCreate()1456     public boolean onCreate() {
1457         mOpenHelper = new DatabaseHelper(getContext());
1458 
1459         // Call getReadableDatabase() to make sure onUpgrade is called
1460         if (VDBG) log("onCreate: calling getReadableDatabase to trigger onUpgrade");
1461         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1462 
1463         // Update APN db on build update
1464         String newBuildId = SystemProperties.get("ro.build.id", null);
1465         if (!TextUtils.isEmpty(newBuildId)) {
1466             // Check if build id has changed
1467             SharedPreferences sp = getContext().getSharedPreferences(BUILD_ID_FILE,
1468                     Context.MODE_PRIVATE);
1469             String oldBuildId = sp.getString(RO_BUILD_ID, "");
1470             if (!newBuildId.equals(oldBuildId)) {
1471                 if (DBG) log("onCreate: build id changed from " + oldBuildId + " to " +
1472                         newBuildId);
1473 
1474                 // Get rid of old preferred apn shared preferences
1475                 SubscriptionManager sm = SubscriptionManager.from(getContext());
1476                 if (sm != null) {
1477                     List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList();
1478                     for (SubscriptionInfo subInfo : subInfoList) {
1479                         SharedPreferences spPrefFile = getContext().getSharedPreferences(
1480                                 PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE);
1481                         if (spPrefFile != null) {
1482                             SharedPreferences.Editor editor = spPrefFile.edit();
1483                             editor.clear();
1484                             editor.apply();
1485                         }
1486                     }
1487                 }
1488 
1489                 // Update APN DB
1490                 updateApnDb();
1491             } else {
1492                 if (VDBG) log("onCreate: build id did not change: " + oldBuildId);
1493             }
1494             sp.edit().putString(RO_BUILD_ID, newBuildId).apply();
1495         } else {
1496             if (VDBG) log("onCreate: newBuildId is empty");
1497         }
1498 
1499         if (VDBG) log("onCreate:- ret true");
1500         return true;
1501     }
1502 
setPreferredApnId(Long id, int subId)1503     private void setPreferredApnId(Long id, int subId) {
1504         SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN,
1505                 Context.MODE_PRIVATE);
1506         SharedPreferences.Editor editor = sp.edit();
1507         editor.putLong(COLUMN_APN_ID + subId, id != null ? id.longValue() : INVALID_APN_ID);
1508         editor.apply();
1509         // remove saved apn if apnId is invalid
1510         if (id == null || id.longValue() == INVALID_APN_ID) {
1511             deletePreferredApn(subId);
1512         }
1513     }
1514 
getPreferredApnId(int subId, boolean checkApnSp)1515     private long getPreferredApnId(int subId, boolean checkApnSp) {
1516         SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN,
1517                 Context.MODE_PRIVATE);
1518         long apnId = sp.getLong(COLUMN_APN_ID + subId, INVALID_APN_ID);
1519         if (apnId == INVALID_APN_ID && checkApnSp) {
1520             apnId = getPreferredApnIdFromApn(subId);
1521             if (apnId != INVALID_APN_ID) {
1522                 setPreferredApnId(apnId, subId);
1523                 deletePreferredApn(subId);
1524             }
1525         }
1526         return apnId;
1527     }
1528 
deletePreferredApnId()1529     private void deletePreferredApnId() {
1530         SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN,
1531                 Context.MODE_PRIVATE);
1532         // before deleting, save actual preferred apns (not the ids) in a separate SP
1533         Map<String, ?> allPrefApnId = sp.getAll();
1534         for (String key : allPrefApnId.keySet()) {
1535             // extract subId from key by removing COLUMN_APN_ID
1536             try {
1537                 int subId = Integer.parseInt(key.replace(COLUMN_APN_ID, ""));
1538                 long apnId = getPreferredApnId(subId, false);
1539                 if (apnId != INVALID_APN_ID) {
1540                     setPreferredApn(apnId, subId);
1541                 }
1542             } catch (Exception e) {
1543                 loge("Skipping over key " + key + " due to exception " + e);
1544             }
1545         }
1546         SharedPreferences.Editor editor = sp.edit();
1547         editor.clear();
1548         editor.apply();
1549     }
1550 
setPreferredApn(Long id, int subId)1551     private void setPreferredApn(Long id, int subId) {
1552         log("setPreferredApn: _id " + id + " subId " + subId);
1553         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1554         // query all unique fields from id
1555         String[] proj = CARRIERS_UNIQUE_FIELDS.toArray(new String[CARRIERS_UNIQUE_FIELDS.size()]);
1556         Cursor c = db.query(CARRIERS_TABLE, proj, "_id=" + id, null, null, null, null);
1557         if (c != null) {
1558             if (c.getCount() == 1) {
1559                 c.moveToFirst();
1560                 SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
1561                         Context.MODE_PRIVATE);
1562                 SharedPreferences.Editor editor = sp.edit();
1563                 // store values of all unique fields to SP
1564                 for (String key : CARRIERS_UNIQUE_FIELDS) {
1565                     editor.putString(key + subId, c.getString(c.getColumnIndex(key)));
1566                 }
1567                 // also store the version number
1568                 editor.putString(DB_VERSION_KEY + subId, "" + DATABASE_VERSION);
1569                 editor.apply();
1570             } else {
1571                 log("setPreferredApn: # matching APNs found " + c.getCount());
1572             }
1573             c.close();
1574         } else {
1575             log("setPreferredApn: No matching APN found");
1576         }
1577     }
1578 
getPreferredApnIdFromApn(int subId)1579     private long getPreferredApnIdFromApn(int subId) {
1580         log("getPreferredApnIdFromApn: for subId " + subId);
1581         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1582         String where = TextUtils.join("=? and ", CARRIERS_UNIQUE_FIELDS) + "=?";
1583         String[] whereArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
1584         SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
1585                 Context.MODE_PRIVATE);
1586         long apnId = INVALID_APN_ID;
1587         int i = 0;
1588         for (String key : CARRIERS_UNIQUE_FIELDS) {
1589             whereArgs[i] = sp.getString(key + subId, null);
1590             if (whereArgs[i] == null) {
1591                 return INVALID_APN_ID;
1592             }
1593             i++;
1594         }
1595         Cursor c = db.query(CARRIERS_TABLE, new String[]{"_id"}, where, whereArgs, null, null,
1596                 null);
1597         if (c != null) {
1598             if (c.getCount() == 1) {
1599                 c.moveToFirst();
1600                 apnId = c.getInt(c.getColumnIndex("_id"));
1601             } else {
1602                 log("getPreferredApnIdFromApn: returning INVALID. # matching APNs found " +
1603                         c.getCount());
1604             }
1605             c.close();
1606         } else {
1607             log("getPreferredApnIdFromApn: returning INVALID. No matching APN found");
1608         }
1609         return apnId;
1610     }
1611 
deletePreferredApn(int subId)1612     private void deletePreferredApn(int subId) {
1613         log("deletePreferredApn: for subId " + subId);
1614         SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
1615                 Context.MODE_PRIVATE);
1616         if (sp.contains(DB_VERSION_KEY + subId)) {
1617             log("deletePreferredApn: apn is stored. Deleting it now for subId " + subId);
1618             SharedPreferences.Editor editor = sp.edit();
1619             editor.remove(DB_VERSION_KEY + subId);
1620             for (String key : CARRIERS_UNIQUE_FIELDS) {
1621                 editor.remove(key + subId);
1622             }
1623             editor.remove(DB_VERSION_KEY + subId);
1624             editor.apply();
1625         }
1626     }
1627 
1628     @Override
query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort)1629     public synchronized Cursor query(Uri url, String[] projectionIn, String selection,
1630             String[] selectionArgs, String sort) {
1631         if (VDBG) log("query: url=" + url + ", projectionIn=" + projectionIn + ", selection="
1632             + selection + "selectionArgs=" + selectionArgs + ", sort=" + sort);
1633         TelephonyManager mTelephonyManager =
1634                 (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
1635         int subId = SubscriptionManager.getDefaultSubscriptionId();
1636         String subIdString;
1637         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1638         qb.setStrict(true); // a little protection from injection attacks
1639         qb.setTables(CARRIERS_TABLE);
1640 
1641         int match = s_urlMatcher.match(url);
1642         switch (match) {
1643             case URL_TELEPHONY_USING_SUBID: {
1644                 subIdString = url.getLastPathSegment();
1645                 try {
1646                     subId = Integer.parseInt(subIdString);
1647                 } catch (NumberFormatException e) {
1648                     loge("NumberFormatException" + e);
1649                     return null;
1650                 }
1651                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1652                 qb.appendWhere(NUMERIC + " = '" + mTelephonyManager.getSimOperator(subId) + "'");
1653                 // FIXME alter the selection to pass subId
1654                 // selection = selection + "and subId = "
1655             }
1656             // intentional fall through from above case
1657             // do nothing
1658             case URL_TELEPHONY: {
1659                 break;
1660             }
1661 
1662             case URL_CURRENT_USING_SUBID: {
1663                 subIdString = url.getLastPathSegment();
1664                 try {
1665                     subId = Integer.parseInt(subIdString);
1666                 } catch (NumberFormatException e) {
1667                     loge("NumberFormatException" + e);
1668                     return null;
1669                 }
1670                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1671                 // FIXME alter the selection to pass subId
1672                 // selection = selection + "and subId = "
1673             }
1674             //intentional fall through from above case
1675             case URL_CURRENT: {
1676                 qb.appendWhere("current IS NOT NULL");
1677                 // do not ignore the selection since MMS may use it.
1678                 //selection = null;
1679                 break;
1680             }
1681 
1682             case URL_ID: {
1683                 qb.appendWhere("_id = " + url.getPathSegments().get(1));
1684                 break;
1685             }
1686 
1687             case URL_PREFERAPN_USING_SUBID:
1688             case URL_PREFERAPN_NO_UPDATE_USING_SUBID: {
1689                 subIdString = url.getLastPathSegment();
1690                 try {
1691                     subId = Integer.parseInt(subIdString);
1692                 } catch (NumberFormatException e) {
1693                     loge("NumberFormatException" + e);
1694                     return null;
1695                 }
1696                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1697             }
1698             //intentional fall through from above case
1699             case URL_PREFERAPN:
1700             case URL_PREFERAPN_NO_UPDATE: {
1701                 qb.appendWhere("_id = " + getPreferredApnId(subId, true));
1702                 break;
1703             }
1704 
1705             case URL_SIMINFO: {
1706                 qb.setTables(SIMINFO_TABLE);
1707                 break;
1708             }
1709 
1710             default: {
1711                 return null;
1712             }
1713         }
1714 
1715         if (match != URL_SIMINFO) {
1716             if (projectionIn != null) {
1717                 for (String column : projectionIn) {
1718                     if (TYPE.equals(column) ||
1719                             MMSC.equals(column) ||
1720                             MMSPROXY.equals(column) ||
1721                             MMSPORT.equals(column) ||
1722                             APN.equals(column)) {
1723                         // noop
1724                     } else {
1725                         checkPermission();
1726                         break;
1727                     }
1728                 }
1729             } else {
1730                 // null returns all columns, so need permission check
1731                 checkPermission();
1732             }
1733         }
1734 
1735         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1736         Cursor ret = null;
1737         try {
1738             // Exclude entries marked deleted
1739             if (CARRIERS_TABLE.equals(qb.getTables())) {
1740                 if (TextUtils.isEmpty(selection)) {
1741                     selection = "";
1742                 } else {
1743                     selection += " and ";
1744                 }
1745                 selection += IS_NOT_USER_DELETED + " and " +
1746                         IS_NOT_USER_DELETED_BUT_PRESENT_IN_XML + " and " +
1747                         IS_NOT_CARRIER_DELETED + " and " +
1748                         IS_NOT_CARRIER_DELETED_BUT_PRESENT_IN_XML;
1749                 if (VDBG) log("query: selection modified to " + selection);
1750             }
1751             ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort);
1752         } catch (SQLException e) {
1753             loge("got exception when querying: " + e);
1754         }
1755         if (ret != null)
1756             ret.setNotificationUri(getContext().getContentResolver(), url);
1757         return ret;
1758     }
1759 
1760     @Override
getType(Uri url)1761     public String getType(Uri url)
1762     {
1763         switch (s_urlMatcher.match(url)) {
1764         case URL_TELEPHONY:
1765         case URL_TELEPHONY_USING_SUBID:
1766             return "vnd.android.cursor.dir/telephony-carrier";
1767 
1768         case URL_ID:
1769             return "vnd.android.cursor.item/telephony-carrier";
1770 
1771         case URL_PREFERAPN_USING_SUBID:
1772         case URL_PREFERAPN_NO_UPDATE_USING_SUBID:
1773         case URL_PREFERAPN:
1774         case URL_PREFERAPN_NO_UPDATE:
1775             return "vnd.android.cursor.item/telephony-carrier";
1776 
1777         default:
1778             throw new IllegalArgumentException("Unknown URL " + url);
1779         }
1780     }
1781 
1782     @Override
insert(Uri url, ContentValues initialValues)1783     public synchronized Uri insert(Uri url, ContentValues initialValues)
1784     {
1785         Uri result = null;
1786         int subId = SubscriptionManager.getDefaultSubscriptionId();
1787 
1788         checkPermission();
1789 
1790         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1791         int match = s_urlMatcher.match(url);
1792         boolean notify = false;
1793         switch (match)
1794         {
1795             case URL_TELEPHONY_USING_SUBID:
1796             {
1797                 String subIdString = url.getLastPathSegment();
1798                 try {
1799                     subId = Integer.parseInt(subIdString);
1800                 } catch (NumberFormatException e) {
1801                     loge("NumberFormatException" + e);
1802                     return result;
1803                 }
1804                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1805             }
1806             //intentional fall through from above case
1807 
1808             case URL_TELEPHONY:
1809             {
1810                 ContentValues values;
1811                 if (initialValues != null) {
1812                     values = new ContentValues(initialValues);
1813                 } else {
1814                     values = new ContentValues();
1815                 }
1816 
1817                 values = DatabaseHelper.setDefaultValue(values);
1818                 if (!values.containsKey(EDITED)) {
1819                     values.put(EDITED, USER_EDITED);
1820                 }
1821 
1822                 try {
1823                     // Replace on conflict so that if same APN is present in db with edited
1824                     // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
1825                     // edited USER/CARRIER_EDITED
1826                     long rowID = db.insertWithOnConflict(CARRIERS_TABLE, null, values,
1827                             SQLiteDatabase.CONFLICT_REPLACE);
1828                     if (rowID >= 0) {
1829                         result = ContentUris.withAppendedId(CONTENT_URI, rowID);
1830                         notify = true;
1831                     }
1832                     if (VDBG) log("insert: inserted " + values.toString() + " rowID = " + rowID);
1833                 } catch (SQLException e) {
1834                     log("insert: exception " + e);
1835                     // Insertion failed which could be due to a conflict. Check if that is the case
1836                     // and merge the entries
1837                     Cursor oldRow = DatabaseHelper.selectConflictingRow(db, CARRIERS_TABLE, values);
1838                     if (oldRow != null) {
1839                         ContentValues mergedValues = new ContentValues();
1840                         DatabaseHelper.mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, values,
1841                                 mergedValues, false, getContext());
1842                         oldRow.close();
1843                         notify = true;
1844                     }
1845                 }
1846 
1847                 break;
1848             }
1849 
1850             case URL_CURRENT_USING_SUBID:
1851             {
1852                 String subIdString = url.getLastPathSegment();
1853                 try {
1854                     subId = Integer.parseInt(subIdString);
1855                 } catch (NumberFormatException e) {
1856                     loge("NumberFormatException" + e);
1857                     return result;
1858                 }
1859                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1860                 // FIXME use subId in the query
1861             }
1862             //intentional fall through from above case
1863 
1864             case URL_CURRENT:
1865             {
1866                 // zero out the previous operator
1867                 db.update(CARRIERS_TABLE, s_currentNullMap, CURRENT + "!=0", null);
1868 
1869                 String numeric = initialValues.getAsString(NUMERIC);
1870                 int updated = db.update(CARRIERS_TABLE, s_currentSetMap,
1871                         NUMERIC + " = '" + numeric + "'", null);
1872 
1873                 if (updated > 0)
1874                 {
1875                     if (VDBG) log("Setting numeric '" + numeric + "' to be the current operator");
1876                 }
1877                 else
1878                 {
1879                     loge("Failed setting numeric '" + numeric + "' to the current operator");
1880                 }
1881                 break;
1882             }
1883 
1884             case URL_PREFERAPN_USING_SUBID:
1885             case URL_PREFERAPN_NO_UPDATE_USING_SUBID:
1886             {
1887                 String subIdString = url.getLastPathSegment();
1888                 try {
1889                     subId = Integer.parseInt(subIdString);
1890                 } catch (NumberFormatException e) {
1891                     loge("NumberFormatException" + e);
1892                     return result;
1893                 }
1894                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1895             }
1896             //intentional fall through from above case
1897 
1898             case URL_PREFERAPN:
1899             case URL_PREFERAPN_NO_UPDATE:
1900             {
1901                 if (initialValues != null) {
1902                     if(initialValues.containsKey(COLUMN_APN_ID)) {
1903                         setPreferredApnId(initialValues.getAsLong(COLUMN_APN_ID), subId);
1904                     }
1905                 }
1906                 break;
1907             }
1908 
1909             case URL_SIMINFO: {
1910                long id = db.insert(SIMINFO_TABLE, null, initialValues);
1911                result = ContentUris.withAppendedId(SubscriptionManager.CONTENT_URI, id);
1912                break;
1913             }
1914         }
1915 
1916         if (notify) {
1917             getContext().getContentResolver().notifyChange(CONTENT_URI, null,
1918                     true, UserHandle.USER_ALL);
1919         }
1920 
1921         return result;
1922     }
1923 
1924     @Override
delete(Uri url, String where, String[] whereArgs)1925     public synchronized int delete(Uri url, String where, String[] whereArgs)
1926     {
1927         int count = 0;
1928         int subId = SubscriptionManager.getDefaultSubscriptionId();
1929         String userOrCarrierEdited = ") and (" +
1930                 EDITED + "=" + USER_EDITED +  " or " +
1931                 EDITED + "=" + CARRIER_EDITED + ")";
1932         String notUserOrCarrierEdited = ") and (" +
1933                 EDITED + "!=" + USER_EDITED +  " and " +
1934                 EDITED + "!=" + CARRIER_EDITED + ")";
1935         ContentValues cv = new ContentValues();
1936         cv.put(EDITED, USER_DELETED);
1937 
1938         checkPermission();
1939 
1940         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1941         int match = s_urlMatcher.match(url);
1942         switch (match)
1943         {
1944             case URL_TELEPHONY_USING_SUBID:
1945             {
1946                  String subIdString = url.getLastPathSegment();
1947                  try {
1948                      subId = Integer.parseInt(subIdString);
1949                  } catch (NumberFormatException e) {
1950                      loge("NumberFormatException" + e);
1951                      throw new IllegalArgumentException("Invalid subId " + url);
1952                  }
1953                  if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1954                 // FIXME use subId in query
1955             }
1956             //intentional fall through from above case
1957 
1958             case URL_TELEPHONY:
1959             {
1960                 // Delete user/carrier edited entries
1961                 count = db.delete(CARRIERS_TABLE, "(" + where + userOrCarrierEdited, whereArgs);
1962                 // Otherwise mark as user deleted instead of deleting
1963                 count += db.update(CARRIERS_TABLE, cv, "(" + where + notUserOrCarrierEdited,
1964                         whereArgs);
1965                 break;
1966             }
1967 
1968             case URL_CURRENT_USING_SUBID: {
1969                 String subIdString = url.getLastPathSegment();
1970                 try {
1971                     subId = Integer.parseInt(subIdString);
1972                 } catch (NumberFormatException e) {
1973                     loge("NumberFormatException" + e);
1974                     throw new IllegalArgumentException("Invalid subId " + url);
1975                 }
1976                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1977                 // FIXME use subId in query
1978             }
1979             //intentional fall through from above case
1980 
1981             case URL_CURRENT:
1982             {
1983                 // Delete user/carrier edited entries
1984                 count = db.delete(CARRIERS_TABLE, "(" + where + userOrCarrierEdited, whereArgs);
1985                 // Otherwise mark as user deleted instead of deleting
1986                 count += db.update(CARRIERS_TABLE, cv, "(" + where + notUserOrCarrierEdited,
1987                         whereArgs);
1988                 break;
1989             }
1990 
1991             case URL_ID:
1992             {
1993                 // Delete user/carrier edited entries
1994                 count = db.delete(CARRIERS_TABLE,
1995                         "(" + _ID + "=?" + userOrCarrierEdited,
1996                         new String[] { url.getLastPathSegment() });
1997                 // Otherwise mark as user deleted instead of deleting
1998                 count += db.update(CARRIERS_TABLE, cv,
1999                         "(" + _ID + "=?" + notUserOrCarrierEdited,
2000                         new String[]{url.getLastPathSegment() });
2001                 break;
2002             }
2003 
2004             case URL_RESTOREAPN_USING_SUBID: {
2005                 String subIdString = url.getLastPathSegment();
2006                 try {
2007                     subId = Integer.parseInt(subIdString);
2008                 } catch (NumberFormatException e) {
2009                     loge("NumberFormatException" + e);
2010                     throw new IllegalArgumentException("Invalid subId " + url);
2011                 }
2012                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
2013                 // FIXME use subId in query
2014             }
2015             case URL_RESTOREAPN: {
2016                 count = 1;
2017                 restoreDefaultAPN(subId);
2018                 break;
2019             }
2020 
2021             case URL_PREFERAPN_USING_SUBID:
2022             case URL_PREFERAPN_NO_UPDATE_USING_SUBID: {
2023                 String subIdString = url.getLastPathSegment();
2024                 try {
2025                     subId = Integer.parseInt(subIdString);
2026                 } catch (NumberFormatException e) {
2027                     loge("NumberFormatException" + e);
2028                     throw new IllegalArgumentException("Invalid subId " + url);
2029                 }
2030                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
2031             }
2032             //intentional fall through from above case
2033 
2034             case URL_PREFERAPN:
2035             case URL_PREFERAPN_NO_UPDATE:
2036             {
2037                 setPreferredApnId((long)INVALID_APN_ID, subId);
2038                 if ((match == URL_PREFERAPN) || (match == URL_PREFERAPN_USING_SUBID)) count = 1;
2039                 break;
2040             }
2041 
2042             case URL_SIMINFO: {
2043                 count = db.delete(SIMINFO_TABLE, where, whereArgs);
2044                 break;
2045             }
2046 
2047             case URL_UPDATE_DB: {
2048                 updateApnDb();
2049                 count = 1;
2050                 break;
2051             }
2052 
2053             default: {
2054                 throw new UnsupportedOperationException("Cannot delete that URL: " + url);
2055             }
2056         }
2057 
2058         if (count > 0) {
2059             getContext().getContentResolver().notifyChange(CONTENT_URI, null,
2060                     true, UserHandle.USER_ALL);
2061         }
2062 
2063         return count;
2064     }
2065 
2066     @Override
update(Uri url, ContentValues values, String where, String[] whereArgs)2067     public synchronized int update(Uri url, ContentValues values, String where, String[] whereArgs)
2068     {
2069         int count = 0;
2070         int uriType = URL_UNKNOWN;
2071         int subId = SubscriptionManager.getDefaultSubscriptionId();
2072 
2073         checkPermission();
2074 
2075         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
2076         int match = s_urlMatcher.match(url);
2077         switch (match)
2078         {
2079             case URL_TELEPHONY_USING_SUBID:
2080             {
2081                  String subIdString = url.getLastPathSegment();
2082                  try {
2083                      subId = Integer.parseInt(subIdString);
2084                  } catch (NumberFormatException e) {
2085                      loge("NumberFormatException" + e);
2086                      throw new IllegalArgumentException("Invalid subId " + url);
2087                  }
2088                  if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
2089                 //FIXME use subId in the query
2090             }
2091             //intentional fall through from above case
2092 
2093             case URL_TELEPHONY:
2094             {
2095                 if (!values.containsKey(EDITED)) {
2096                     values.put(EDITED, USER_EDITED);
2097                 }
2098 
2099                 // Replace on conflict so that if same APN is present in db with edited
2100                 // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
2101                 // edited USER/CARRIER_EDITED
2102                 count = db.updateWithOnConflict(CARRIERS_TABLE, values, where, whereArgs,
2103                         SQLiteDatabase.CONFLICT_REPLACE);
2104                 break;
2105             }
2106 
2107             case URL_CURRENT_USING_SUBID:
2108             {
2109                 String subIdString = url.getLastPathSegment();
2110                 try {
2111                     subId = Integer.parseInt(subIdString);
2112                 } catch (NumberFormatException e) {
2113                     loge("NumberFormatException" + e);
2114                     throw new IllegalArgumentException("Invalid subId " + url);
2115                 }
2116                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
2117                 //FIXME use subId in the query
2118             }
2119             //intentional fall through from above case
2120 
2121             case URL_CURRENT:
2122             {
2123                 if (!values.containsKey(EDITED)) {
2124                     values.put(EDITED, USER_EDITED);
2125                 }
2126                 // Replace on conflict so that if same APN is present in db with edited
2127                 // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
2128                 // edited USER/CARRIER_EDITED
2129                 count = db.updateWithOnConflict(CARRIERS_TABLE, values, where, whereArgs,
2130                         SQLiteDatabase.CONFLICT_REPLACE);
2131                 break;
2132             }
2133 
2134             case URL_ID:
2135             {
2136                 if (where != null || whereArgs != null) {
2137                     throw new UnsupportedOperationException(
2138                             "Cannot update URL " + url + " with a where clause");
2139                 }
2140                 if (!values.containsKey(EDITED)) {
2141                     values.put(EDITED, USER_EDITED);
2142                 }
2143                 // Replace on conflict so that if same APN is present in db with edited
2144                 // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
2145                 // edited USER/CARRIER_EDITED
2146                 count = db.updateWithOnConflict(CARRIERS_TABLE, values,
2147                         _ID + "=?", new String[] { url.getLastPathSegment() },
2148                         SQLiteDatabase.CONFLICT_REPLACE);
2149                 break;
2150             }
2151 
2152             case URL_PREFERAPN_USING_SUBID:
2153             case URL_PREFERAPN_NO_UPDATE_USING_SUBID:
2154             {
2155                 String subIdString = url.getLastPathSegment();
2156                 try {
2157                     subId = Integer.parseInt(subIdString);
2158                 } catch (NumberFormatException e) {
2159                     loge("NumberFormatException" + e);
2160                     throw new IllegalArgumentException("Invalid subId " + url);
2161                 }
2162                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
2163             }
2164 
2165             case URL_PREFERAPN:
2166             case URL_PREFERAPN_NO_UPDATE:
2167             {
2168                 if (values != null) {
2169                     if (values.containsKey(COLUMN_APN_ID)) {
2170                         setPreferredApnId(values.getAsLong(COLUMN_APN_ID), subId);
2171                         if ((match == URL_PREFERAPN) ||
2172                                 (match == URL_PREFERAPN_USING_SUBID)) {
2173                             count = 1;
2174                         }
2175                     }
2176                 }
2177                 break;
2178             }
2179 
2180             case URL_SIMINFO: {
2181                 count = db.update(SIMINFO_TABLE, values, where, whereArgs);
2182                 uriType = URL_SIMINFO;
2183                 break;
2184             }
2185 
2186             default: {
2187                 throw new UnsupportedOperationException("Cannot update that URL: " + url);
2188             }
2189         }
2190 
2191         if (count > 0) {
2192             switch (uriType) {
2193                 case URL_SIMINFO:
2194                     getContext().getContentResolver().notifyChange(
2195                             SubscriptionManager.CONTENT_URI, null, true, UserHandle.USER_ALL);
2196                     break;
2197                 default:
2198                     getContext().getContentResolver().notifyChange(
2199                             CONTENT_URI, null, true, UserHandle.USER_ALL);
2200             }
2201         }
2202 
2203         return count;
2204     }
2205 
checkPermission()2206     private void checkPermission() {
2207         int status = getContext().checkCallingOrSelfPermission(
2208                 "android.permission.WRITE_APN_SETTINGS");
2209         if (status == PackageManager.PERMISSION_GRANTED) {
2210             return;
2211         }
2212 
2213         PackageManager packageManager = getContext().getPackageManager();
2214         String[] packages = packageManager.getPackagesForUid(Binder.getCallingUid());
2215 
2216         TelephonyManager telephonyManager =
2217                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
2218         for (String pkg : packages) {
2219             if (telephonyManager.checkCarrierPrivilegesForPackage(pkg) ==
2220                     TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
2221                 return;
2222             }
2223         }
2224         throw new SecurityException("No permission to write APN settings");
2225     }
2226 
2227     private DatabaseHelper mOpenHelper;
2228 
restoreDefaultAPN(int subId)2229     private void restoreDefaultAPN(int subId) {
2230         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
2231 
2232         try {
2233             db.delete(CARRIERS_TABLE, null, null);
2234         } catch (SQLException e) {
2235             loge("got exception when deleting to restore: " + e);
2236         }
2237         setPreferredApnId((long) INVALID_APN_ID, subId);
2238         mOpenHelper.initDatabase(db);
2239     }
2240 
updateApnDb()2241     private synchronized void updateApnDb() {
2242         if (!mOpenHelper.apnDbUpdateNeeded()) {
2243             log("Skipping apn db update since apn-conf has not changed.");
2244             return;
2245         }
2246 
2247         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
2248 
2249         // Delete preferred APN for all subIds
2250         deletePreferredApnId();
2251 
2252         // Delete entries in db
2253         try {
2254             if (VDBG) log("updateApnDb: deleting edited=UNEDITED entries");
2255             db.delete(CARRIERS_TABLE, IS_UNEDITED, null);
2256         } catch (SQLException e) {
2257             loge("got exception when deleting to update: " + e);
2258         }
2259 
2260         mOpenHelper.initDatabase(db);
2261 
2262         // Notify listereners of DB change since DB has been updated
2263         getContext().getContentResolver().notifyChange(
2264                 CONTENT_URI, null, true, UserHandle.USER_ALL);
2265 
2266     }
2267 
2268     /**
2269      * Log with debug
2270      *
2271      * @param s is string log
2272      */
log(String s)2273     private static void log(String s) {
2274         Log.d(TAG, s);
2275     }
2276 
loge(String s)2277     private static void loge(String s) {
2278         Log.e(TAG, s);
2279     }
2280 }
2281