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
18package com.android.providers.telephony;
19
20import android.content.ContentProvider;
21import android.content.ContentUris;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.SharedPreferences;
25import android.content.UriMatcher;
26import android.content.pm.PackageManager;
27import android.content.res.Resources;
28import android.content.res.XmlResourceParser;
29import android.database.Cursor;
30import android.database.SQLException;
31import android.database.sqlite.SQLiteDatabase;
32import android.database.sqlite.SQLiteException;
33import android.database.sqlite.SQLiteOpenHelper;
34import android.database.sqlite.SQLiteQueryBuilder;
35import android.net.Uri;
36import android.os.Binder;
37import android.os.Environment;
38import android.os.FileUtils;
39import android.os.SystemProperties;
40import android.os.UserHandle;
41import android.telephony.ServiceState;
42import android.telephony.SubscriptionInfo;
43import android.telephony.SubscriptionManager;
44import android.telephony.TelephonyManager;
45import android.text.TextUtils;
46import android.util.Log;
47import android.util.Xml;
48
49import com.android.internal.util.XmlUtils;
50
51import org.xmlpull.v1.XmlPullParser;
52import org.xmlpull.v1.XmlPullParserException;
53
54import java.io.File;
55import java.io.FileNotFoundException;
56import java.io.FileReader;
57import java.io.IOException;
58import java.util.ArrayList;
59import java.util.Arrays;
60import java.util.List;
61import java.util.Map;
62
63import static android.provider.Telephony.Carriers.*;
64
65public 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);
175        s_currentNullMap.put(CURRENT, "0");
176
177        s_currentSetMap = new ContentValues(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         */
190        public DatabaseHelper(Context context) {
191            super(context, DATABASE_NAME, null, getVersion(context));
192            mContext = context;
193        }
194
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
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
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
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
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
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
342        private long getApnConfChecksum() {
343            SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
344            return sp.getLong(APN_CONF_CHECKSUM, -1);
345        }
346
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
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         */
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         */
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
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
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
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
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
792        private String queryValFirst(String field) {
793            return field + "=?";
794        }
795
796        private String queryVal(String field) {
797            return " and " + field + "=?";
798        }
799
800        private String queryValOrNull(String field) {
801            return " and (" + field + "=? or " + field + " is null)";
802        }
803
804        private String queryVal2OrNull(String field) {
805            return " and (" + field + "=? or " + field + "=? or " + field + " is null)";
806        }
807
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
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
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
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         */
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
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
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
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         */
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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     */
2274    private static void log(String s) {
2275        Log.d(TAG, s);
2276    }
2277
2278    private static void loge(String s) {
2279        Log.e(TAG, s);
2280    }
2281}
2282