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