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