TelephonyProvider.java revision 4e18088a753e512a167f80c7fb469490334590c6
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.Config; 36import android.util.Log; 37import android.util.Xml; 38 39import com.android.internal.util.XmlUtils; 40 41import org.xmlpull.v1.XmlPullParser; 42import org.xmlpull.v1.XmlPullParserException; 43 44import java.io.File; 45import java.io.FileNotFoundException; 46import java.io.FileReader; 47import java.io.IOException; 48 49public 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 */ 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 140 initDatabase(db); 141 } 142 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 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 */ 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 */ 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 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 303 public boolean onCreate() { 304 mOpenHelper = new DatabaseHelper(getContext()); 305 return true; 306 } 307 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.apply(); 313 } 314 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 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 // do not ignore the selection since MMS may use it. 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 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 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 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 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 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 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