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