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