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