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