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