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