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