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