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