• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.providers.telephony;
19 
20 import android.content.ContentProvider;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.SharedPreferences;
25 import android.content.UriMatcher;
26 import android.content.res.Resources;
27 import android.content.res.XmlResourceParser;
28 import android.database.Cursor;
29 import android.database.sqlite.SQLiteDatabase;
30 import android.database.sqlite.SQLiteOpenHelper;
31 import android.database.sqlite.SQLiteQueryBuilder;
32 import android.net.Uri;
33 import android.os.Environment;
34 import android.provider.Telephony;
35 import android.util.Config;
36 import android.util.Log;
37 import android.util.Xml;
38 
39 import com.android.internal.util.XmlUtils;
40 
41 import org.xmlpull.v1.XmlPullParser;
42 import org.xmlpull.v1.XmlPullParserException;
43 
44 import java.io.File;
45 import java.io.FileNotFoundException;
46 import java.io.FileReader;
47 import java.io.IOException;
48 
49 public 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          */
DatabaseHelper(Context context)97         public DatabaseHelper(Context context) {
98             super(context, DATABASE_NAME, null, getVersion(context));
99             mContext = context;
100         }
101 
getVersion(Context context)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
onCreate(SQLiteDatabase db)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 
initDatabase(SQLiteDatabase db)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
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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          */
getRow(XmlPullParser parser)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          */
loadApns(SQLiteDatabase db, XmlPullParser parser)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 
insertAddingDefaults(SQLiteDatabase db, String table, ContentValues row)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
onCreate()303     public boolean onCreate() {
304         mOpenHelper = new DatabaseHelper(getContext());
305         return true;
306     }
307 
setPreferredApnId(Long id)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 
getPreferredApnId()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
query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort)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
getType(Uri url)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
insert(Uri url, ContentValues initialValues)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
delete(Uri url, String where, String[] whereArgs)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
update(Uri url, ContentValues values, String where, String[] whereArgs)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 
checkPermission()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 
restoreDefaultAPN()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