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