TelephonyProvider.java revision 607ead75c55146786d79e09f36d069cf3a81eb60
1/* //device/content/providers/telephony/TelephonyProvider.java
2**
3** Copyright 2006, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18package com.android.providers.telephony;
19
20import android.content.ContentProvider;
21import android.content.ContentUris;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.SharedPreferences;
25import android.content.UriMatcher;
26import android.content.res.Resources;
27import android.content.res.XmlResourceParser;
28import android.database.Cursor;
29import android.database.sqlite.SQLiteDatabase;
30import android.database.sqlite.SQLiteOpenHelper;
31import android.database.sqlite.SQLiteQueryBuilder;
32import android.net.Uri;
33import android.os.Environment;
34import android.os.FileUtils;
35import android.provider.Telephony;
36import android.util.Config;
37import android.util.Log;
38import android.util.Xml;
39
40import com.android.internal.telephony.BaseCommands;
41import com.android.internal.telephony.Phone;
42import com.android.internal.util.XmlUtils;
43
44import org.xmlpull.v1.XmlPullParser;
45import org.xmlpull.v1.XmlPullParserException;
46
47import java.io.File;
48import java.io.FileNotFoundException;
49import java.io.FileReader;
50import java.io.IOException;
51
52
53public class TelephonyProvider extends ContentProvider
54{
55    private static final String DATABASE_NAME = "telephony.db";
56    private static final boolean DBG = true;
57
58    private static final int DATABASE_VERSION = 6 << 16;
59    private static final int URL_TELEPHONY = 1;
60    private static final int URL_CURRENT = 2;
61    private static final int URL_ID = 3;
62    private static final int URL_RESTOREAPN = 4;
63    private static final int URL_PREFERAPN = 5;
64
65    private static final String TAG = "TelephonyProvider";
66    private static final String CARRIERS_TABLE = "carriers";
67
68    private static final String PREF_FILE = "preferred-apn";
69    private static final String COLUMN_APN_ID = "apn_id";
70    private static final String APN_CONFIG_CHECKSUM = "apn_conf_checksum";
71
72    private static final String PARTNER_APNS_PATH = "etc/apns-conf.xml";
73
74    private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
75
76    private static final ContentValues s_currentNullMap;
77    private static final ContentValues s_currentSetMap;
78
79    static {
80        s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
81        s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);
82        s_urlMatcher.addURI("telephony", "carriers/#", URL_ID);
83        s_urlMatcher.addURI("telephony", "carriers/restore", URL_RESTOREAPN);
84        s_urlMatcher.addURI("telephony", "carriers/preferapn", URL_PREFERAPN);
85
86        s_currentNullMap = new ContentValues(1);
87        s_currentNullMap.put("current", (Long) null);
88
89        s_currentSetMap = new ContentValues(1);
90        s_currentSetMap.put("current", "1");
91    }
92
93    private static class DatabaseHelper extends SQLiteOpenHelper {
94        // Context to access resources with
95        private Context mContext;
96
97        /**
98         * DatabaseHelper helper class for loading apns into a database.
99         *
100         * @param context of the user.
101         */
102        public DatabaseHelper(Context context) {
103            super(context, DATABASE_NAME, null, getVersion(context));
104            mContext = context;
105        }
106
107        private static int getVersion(Context context) {
108            // Get the database version, combining a static schema version and the XML version
109            Resources r = context.getResources();
110            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
111            try {
112                XmlUtils.beginDocument(parser, "apns");
113                int publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
114                return DATABASE_VERSION | publicversion;
115            } catch (Exception e) {
116                Log.e(TAG, "Can't get version of APN database", e);
117                return DATABASE_VERSION;
118            } finally {
119                parser.close();
120            }
121        }
122
123        @Override
124        public void onCreate(SQLiteDatabase db) {
125            // Set up the database schema
126            db.execSQL("CREATE TABLE " + CARRIERS_TABLE +
127                "(_id INTEGER PRIMARY KEY," +
128                    "name TEXT," +
129                    "numeric TEXT," +
130                    "mcc TEXT," +
131                    "mnc TEXT," +
132                    "apn TEXT," +
133                    "user TEXT," +
134                    "server TEXT," +
135                    "password TEXT," +
136                    "proxy TEXT," +
137                    "port TEXT," +
138                    "mmsproxy TEXT," +
139                    "mmsport TEXT," +
140                    "mmsc TEXT," +
141                    "authtype INTEGER," +
142                    "type TEXT," +
143                    "current INTEGER," +
144                    "protocol TEXT," +
145                    "roaming_protocol TEXT);");
146
147            initDatabase(db);
148        }
149
150        private void initDatabase(SQLiteDatabase db) {
151            // Read internal APNS data
152            Resources r = mContext.getResources();
153            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
154            int publicversion = -1;
155            try {
156                XmlUtils.beginDocument(parser, "apns");
157                publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
158                loadApns(db, parser);
159            } catch (Exception e) {
160                Log.e(TAG, "Got exception while loading APN database.", e);
161            } finally {
162                parser.close();
163            }
164
165           // Read external APNS data (partner-provided)
166            XmlPullParser confparser = null;
167            // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
168            File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
169            FileReader confreader = null;
170            try {
171                confreader = new FileReader(confFile);
172                confparser = Xml.newPullParser();
173                confparser.setInput(confreader);
174                XmlUtils.beginDocument(confparser, "apns");
175
176                // Sanity check. Force internal version and confidential versions to agree
177                int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
178                if (publicversion != confversion) {
179                    throw new IllegalStateException("Internal APNS file version doesn't match "
180                            + confFile.getAbsolutePath());
181                }
182
183                loadApns(db, confparser);
184            } catch (FileNotFoundException e) {
185                // It's ok if the file isn't found. It means there isn't a confidential file
186                // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
187            } catch (Exception e) {
188                Log.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e);
189            } finally {
190                try { if (confreader != null) confreader.close(); } catch (IOException e) { }
191            }
192        }
193
194        @Override
195        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
196            if (oldVersion < (5 << 16 | 6)) {
197                // 5 << 16 is the Database version and 6 in the xml version.
198
199                // This change adds a new authtype column to the database.
200                // The auth type column can have 4 values: 0 (None), 1 (PAP), 2 (CHAP)
201                // 3 (PAP or CHAP). To avoid breaking compatibility, with already working
202                // APNs, the unset value (-1) will be used. If the value is -1.
203                // the authentication will default to 0 (if no user / password) is specified
204                // or to 3. Currently, there have been no reported problems with
205                // pre-configured APNs and hence it is set to -1 for them. Similarly,
206                // if the user, has added a new APN, we set the authentication type
207                // to -1.
208
209                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
210                        " ADD COLUMN authtype INTEGER DEFAULT -1;");
211
212                oldVersion = 5 << 16 | 6;
213            }
214            if (oldVersion < (6 << 16 | 6)) {
215                // Add protcol fields to the APN. The XML file does not change.
216                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
217                        " ADD COLUMN protocol TEXT DEFAULT IP;");
218                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
219                        " ADD COLUMN roaming_protocol TEXT DEFAULT IP;");
220                oldVersion = 6 << 16 | 6;
221            }
222        }
223
224        /**
225         * Gets the next row of apn values.
226         *
227         * @param parser the parser
228         * @return the row or null if it's not an apn
229         */
230        private ContentValues getRow(XmlPullParser parser) {
231            if (!"apn".equals(parser.getName())) {
232                return null;
233            }
234
235            ContentValues map = new ContentValues();
236
237            String mcc = parser.getAttributeValue(null, "mcc");
238            String mnc = parser.getAttributeValue(null, "mnc");
239            String numeric = mcc + mnc;
240
241            map.put(Telephony.Carriers.NUMERIC,numeric);
242            map.put(Telephony.Carriers.MCC, mcc);
243            map.put(Telephony.Carriers.MNC, mnc);
244            map.put(Telephony.Carriers.NAME, parser.getAttributeValue(null, "carrier"));
245            map.put(Telephony.Carriers.APN, parser.getAttributeValue(null, "apn"));
246            map.put(Telephony.Carriers.USER, parser.getAttributeValue(null, "user"));
247            map.put(Telephony.Carriers.SERVER, parser.getAttributeValue(null, "server"));
248            map.put(Telephony.Carriers.PASSWORD, parser.getAttributeValue(null, "password"));
249
250            // do not add NULL to the map so that insert() will set the default value
251            String proxy = parser.getAttributeValue(null, "proxy");
252            if (proxy != null) {
253                map.put(Telephony.Carriers.PROXY, proxy);
254            }
255            String port = parser.getAttributeValue(null, "port");
256            if (port != null) {
257                map.put(Telephony.Carriers.PORT, port);
258            }
259            String mmsproxy = parser.getAttributeValue(null, "mmsproxy");
260            if (mmsproxy != null) {
261                map.put(Telephony.Carriers.MMSPROXY, mmsproxy);
262            }
263            String mmsport = parser.getAttributeValue(null, "mmsport");
264            if (mmsport != null) {
265                map.put(Telephony.Carriers.MMSPORT, mmsport);
266            }
267            map.put(Telephony.Carriers.MMSC, parser.getAttributeValue(null, "mmsc"));
268            String type = parser.getAttributeValue(null, "type");
269            if (type != null) {
270                map.put(Telephony.Carriers.TYPE, type);
271            }
272
273            String auth = parser.getAttributeValue(null, "authtype");
274            if (auth != null) {
275                map.put(Telephony.Carriers.AUTH_TYPE, Integer.parseInt(auth));
276            }
277
278            String protocol = parser.getAttributeValue(null, "protocol");
279            if (protocol != null) {
280                map.put(Telephony.Carriers.PROTOCOL, protocol);
281            }
282
283            String roamingProtocol = parser.getAttributeValue(null, "roaming_protocol");
284            if (roamingProtocol != null) {
285                map.put(Telephony.Carriers.ROAMING_PROTOCOL, roamingProtocol);
286            }
287
288            return map;
289        }
290
291        /*
292         * Loads apns from xml file into the database
293         *
294         * @param db the sqlite database to write to
295         * @param parser the xml parser
296         *
297         */
298        private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
299            if (parser != null) {
300                try {
301                    while (true) {
302                        XmlUtils.nextElement(parser);
303                        ContentValues row = getRow(parser);
304                        if (row != null) {
305                            insertAddingDefaults(db, CARRIERS_TABLE, row);
306                        } else {
307                            break;  // do we really want to skip the rest of the file?
308                        }
309                    }
310                } catch (XmlPullParserException e)  {
311                    Log.e(TAG, "Got execption while getting perferred time zone.", e);
312                } catch (IOException e) {
313                    Log.e(TAG, "Got execption while getting perferred time zone.", e);
314                }
315            }
316        }
317
318        private void insertAddingDefaults(SQLiteDatabase db, String table, ContentValues row) {
319            // Initialize defaults if any
320            if (row.containsKey(Telephony.Carriers.AUTH_TYPE) == false) {
321                row.put(Telephony.Carriers.AUTH_TYPE, -1);
322            }
323            if (row.containsKey(Telephony.Carriers.PROTOCOL) == false) {
324                row.put(Telephony.Carriers.PROTOCOL, "IP");
325            }
326            if (row.containsKey(Telephony.Carriers.ROAMING_PROTOCOL) == false) {
327                row.put(Telephony.Carriers.ROAMING_PROTOCOL, "IP");
328            }
329            db.insert(CARRIERS_TABLE, null, row);
330        }
331    }
332
333    @Override
334    public boolean onCreate() {
335        long oldCheckSum = getAPNConfigCheckSum();
336        File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
337        long newCheckSum = -1L;
338
339        if (DBG) {
340            Log.w(TAG, "onCreate: confFile=" + confFile.getAbsolutePath() +
341                    " oldCheckSum=" + oldCheckSum);
342        }
343        mOpenHelper = new DatabaseHelper(getContext());
344
345        if (isLteOnCdma()) {
346            // Check to see if apns-conf.xml file changed. If so, generate db again.
347            //
348            // TODO: Generalize so we can handle apns-conf.xml updates
349            // and preserve any modifications the user might make. For
350            // now its safe on LteOnCdma devices because the user cannot
351            // make changes.
352            try {
353                newCheckSum = FileUtils.checksumCrc32(confFile);
354                if (DBG) Log.w(TAG, "onCreate: newCheckSum=" + newCheckSum);
355                if (oldCheckSum != newCheckSum) {
356                    Log.w(TAG, "Rebuilding Telephony.db");
357                    restoreDefaultAPN();
358                    setAPNConfigCheckSum(newCheckSum);
359                }
360            } catch (FileNotFoundException e) {
361                Log.e(TAG, "FileNotFoundException: '" + confFile.getAbsolutePath() + "'", e);
362            } catch (IOException e) {
363                Log.e(TAG, "IOException: '" + confFile.getAbsolutePath() + "'", e);
364            }
365        }
366        return true;
367    }
368
369    private boolean isLteOnCdma() {
370        return BaseCommands.getLteOnCdmaModeStatic() == Phone.LTE_ON_CDMA_TRUE;
371    }
372
373    private void setPreferredApnId(Long id) {
374        SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
375        SharedPreferences.Editor editor = sp.edit();
376        editor.putLong(COLUMN_APN_ID, id != null ? id.longValue() : -1);
377        editor.apply();
378    }
379
380    private long getPreferredApnId() {
381        SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
382        return sp.getLong(COLUMN_APN_ID, -1);
383    }
384
385    private long getAPNConfigCheckSum() {
386        SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
387        return sp.getLong(APN_CONFIG_CHECKSUM, -1);
388    }
389
390    private void setAPNConfigCheckSum(long id) {
391        SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
392        SharedPreferences.Editor editor = sp.edit();
393        editor.putLong(APN_CONFIG_CHECKSUM, id);
394        editor.apply();
395    }
396
397    @Override
398    public Cursor query(Uri url, String[] projectionIn, String selection,
399            String[] selectionArgs, String sort) {
400        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
401        qb.setTables("carriers");
402
403        int match = s_urlMatcher.match(url);
404        switch (match) {
405            // do nothing
406            case URL_TELEPHONY: {
407                break;
408            }
409
410
411            case URL_CURRENT: {
412                qb.appendWhere("current IS NOT NULL");
413                // do not ignore the selection since MMS may use it.
414                //selection = null;
415                break;
416            }
417
418            case URL_ID: {
419                qb.appendWhere("_id = " + url.getPathSegments().get(1));
420                break;
421            }
422
423            case URL_PREFERAPN: {
424                qb.appendWhere("_id = " + getPreferredApnId());
425                break;
426            }
427
428            default: {
429                return null;
430            }
431        }
432
433        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
434        Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort);
435        ret.setNotificationUri(getContext().getContentResolver(), url);
436        return ret;
437    }
438
439    @Override
440    public String getType(Uri url)
441    {
442        switch (s_urlMatcher.match(url)) {
443        case URL_TELEPHONY:
444            return "vnd.android.cursor.dir/telephony-carrier";
445
446        case URL_ID:
447            return "vnd.android.cursor.item/telephony-carrier";
448
449        case URL_PREFERAPN:
450            return "vnd.android.cursor.item/telephony-carrier";
451
452        default:
453            throw new IllegalArgumentException("Unknown URL " + url);
454        }
455    }
456
457    @Override
458    public Uri insert(Uri url, ContentValues initialValues)
459    {
460        Uri result = null;
461
462        checkPermission();
463
464        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
465        int match = s_urlMatcher.match(url);
466        boolean notify = false;
467        switch (match)
468        {
469            case URL_TELEPHONY:
470            {
471                ContentValues values;
472                if (initialValues != null) {
473                    values = new ContentValues(initialValues);
474                } else {
475                    values = new ContentValues();
476                }
477
478                // TODO Review this. This code should probably not bet here.
479                // It is valid for the database to return a null string.
480                if (!values.containsKey(Telephony.Carriers.NAME)) {
481                    values.put(Telephony.Carriers.NAME, "");
482                }
483                if (!values.containsKey(Telephony.Carriers.APN)) {
484                    values.put(Telephony.Carriers.APN, "");
485                }
486                if (!values.containsKey(Telephony.Carriers.PORT)) {
487                    values.put(Telephony.Carriers.PORT, "");
488                }
489                if (!values.containsKey(Telephony.Carriers.PROXY)) {
490                    values.put(Telephony.Carriers.PROXY, "");
491                }
492                if (!values.containsKey(Telephony.Carriers.USER)) {
493                    values.put(Telephony.Carriers.USER, "");
494                }
495                if (!values.containsKey(Telephony.Carriers.SERVER)) {
496                    values.put(Telephony.Carriers.SERVER, "");
497                }
498                if (!values.containsKey(Telephony.Carriers.PASSWORD)) {
499                    values.put(Telephony.Carriers.PASSWORD, "");
500                }
501                if (!values.containsKey(Telephony.Carriers.MMSPORT)) {
502                    values.put(Telephony.Carriers.MMSPORT, "");
503                }
504                if (!values.containsKey(Telephony.Carriers.MMSPROXY)) {
505                    values.put(Telephony.Carriers.MMSPROXY, "");
506                }
507                if (!values.containsKey(Telephony.Carriers.AUTH_TYPE)) {
508                    values.put(Telephony.Carriers.AUTH_TYPE, -1);
509                }
510                if (!values.containsKey(Telephony.Carriers.PROTOCOL)) {
511                    values.put(Telephony.Carriers.PROTOCOL, "IP");
512                }
513                if (!values.containsKey(Telephony.Carriers.ROAMING_PROTOCOL)) {
514                    values.put(Telephony.Carriers.ROAMING_PROTOCOL, "IP");
515                }
516
517
518                long rowID = db.insert(CARRIERS_TABLE, null, values);
519                if (rowID > 0)
520                {
521                    result = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, rowID);
522                    notify = true;
523                }
524
525                if (Config.LOGD) Log.d(TAG, "inserted " + values.toString() + " rowID = " + rowID);
526                break;
527            }
528
529            case URL_CURRENT:
530            {
531                // null out the previous operator
532                db.update("carriers", s_currentNullMap, "current IS NOT NULL", null);
533
534                String numeric = initialValues.getAsString("numeric");
535                int updated = db.update("carriers", s_currentSetMap,
536                        "numeric = '" + numeric + "'", null);
537
538                if (updated > 0)
539                {
540                    if (Config.LOGD) {
541                        Log.d(TAG, "Setting numeric '" + numeric + "' to be the current operator");
542                    }
543                }
544                else
545                {
546                    Log.e(TAG, "Failed setting numeric '" + numeric + "' to the current operator");
547                }
548                break;
549            }
550
551            case URL_PREFERAPN:
552            {
553                if (initialValues != null) {
554                    if(initialValues.containsKey(COLUMN_APN_ID)) {
555                        setPreferredApnId(initialValues.getAsLong(COLUMN_APN_ID));
556                    }
557                }
558                break;
559            }
560        }
561
562        if (notify) {
563            getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
564        }
565
566        return result;
567    }
568
569    @Override
570    public int delete(Uri url, String where, String[] whereArgs)
571    {
572        int count;
573
574        checkPermission();
575
576        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
577        int match = s_urlMatcher.match(url);
578        switch (match)
579        {
580            case URL_TELEPHONY:
581            {
582                count = db.delete(CARRIERS_TABLE, where, whereArgs);
583                break;
584            }
585
586            case URL_CURRENT:
587            {
588                count = db.delete(CARRIERS_TABLE, where, whereArgs);
589                break;
590            }
591
592            case URL_ID:
593            {
594                count = db.delete(CARRIERS_TABLE, Telephony.Carriers._ID + "=?",
595                        new String[] { url.getLastPathSegment() });
596                break;
597            }
598
599            case URL_RESTOREAPN: {
600                count = 1;
601                restoreDefaultAPN();
602                break;
603            }
604
605            case URL_PREFERAPN:
606            {
607                setPreferredApnId((long)-1);
608                count = 1;
609                break;
610            }
611
612            default: {
613                throw new UnsupportedOperationException("Cannot delete that URL: " + url);
614            }
615        }
616
617        if (count > 0) {
618            getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
619        }
620
621        return count;
622    }
623
624    @Override
625    public int update(Uri url, ContentValues values, String where, String[] whereArgs)
626    {
627        int count = 0;
628
629        checkPermission();
630
631        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
632        int match = s_urlMatcher.match(url);
633        switch (match)
634        {
635            case URL_TELEPHONY:
636            {
637                count = db.update(CARRIERS_TABLE, values, where, whereArgs);
638                break;
639            }
640
641            case URL_CURRENT:
642            {
643                count = db.update(CARRIERS_TABLE, values, where, whereArgs);
644                break;
645            }
646
647            case URL_ID:
648            {
649                if (where != null || whereArgs != null) {
650                    throw new UnsupportedOperationException(
651                            "Cannot update URL " + url + " with a where clause");
652                }
653                count = db.update(CARRIERS_TABLE, values, Telephony.Carriers._ID + "=?",
654                        new String[] { url.getLastPathSegment() });
655                break;
656            }
657
658            case URL_PREFERAPN:
659            {
660                if (values != null) {
661                    if (values.containsKey(COLUMN_APN_ID)) {
662                        setPreferredApnId(values.getAsLong(COLUMN_APN_ID));
663                        count = 1;
664                    }
665                }
666                break;
667            }
668
669            default: {
670                throw new UnsupportedOperationException("Cannot update that URL: " + url);
671            }
672        }
673
674        if (count > 0) {
675            getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
676        }
677
678        return count;
679    }
680
681    private void checkPermission() {
682        // Check the permissions
683        getContext().enforceCallingOrSelfPermission("android.permission.WRITE_APN_SETTINGS",
684                "No permission to write APN settings");
685    }
686
687    private DatabaseHelper mOpenHelper;
688
689    private void restoreDefaultAPN() {
690        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
691
692        db.delete(CARRIERS_TABLE, null, null);
693        setPreferredApnId((long)-1);
694        mOpenHelper.initDatabase(db);
695    }
696}
697