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