1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.messaging.sms; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.content.res.XmlResourceParser; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteDatabase; 25 import android.database.sqlite.SQLiteException; 26 import android.database.sqlite.SQLiteOpenHelper; 27 import android.provider.Telephony; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import com.android.messaging.R; 32 import com.android.messaging.datamodel.data.ParticipantData; 33 import com.android.messaging.util.LogUtil; 34 import com.android.messaging.util.PhoneUtils; 35 import com.google.common.collect.Lists; 36 37 import java.io.File; 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /* 42 * Database helper class for looking up APNs. This database has a single table 43 * which stores the APNs that are initially created from an xml file. 44 */ 45 public class ApnDatabase extends SQLiteOpenHelper { 46 private static final int DB_VERSION = 3; // added sub_id columns 47 48 private static final String TAG = LogUtil.BUGLE_TAG; 49 50 private static final boolean DEBUG = false; 51 52 private static Context sContext; 53 private static ApnDatabase sApnDatabase; 54 55 private static final String APN_DATABASE_NAME = "apn.db"; 56 57 /** table for carrier APN's */ 58 public static final String APN_TABLE = "apn"; 59 60 // APN table 61 private static final String APN_TABLE_SQL = 62 "CREATE TABLE " + APN_TABLE + 63 "(_id INTEGER PRIMARY KEY," + 64 Telephony.Carriers.NAME + " TEXT," + 65 Telephony.Carriers.NUMERIC + " TEXT," + 66 Telephony.Carriers.MCC + " TEXT," + 67 Telephony.Carriers.MNC + " TEXT," + 68 Telephony.Carriers.APN + " TEXT," + 69 Telephony.Carriers.USER + " TEXT," + 70 Telephony.Carriers.SERVER + " TEXT," + 71 Telephony.Carriers.PASSWORD + " TEXT," + 72 Telephony.Carriers.PROXY + " TEXT," + 73 Telephony.Carriers.PORT + " TEXT," + 74 Telephony.Carriers.MMSPROXY + " TEXT," + 75 Telephony.Carriers.MMSPORT + " TEXT," + 76 Telephony.Carriers.MMSC + " TEXT," + 77 Telephony.Carriers.AUTH_TYPE + " INTEGER," + 78 Telephony.Carriers.TYPE + " TEXT," + 79 Telephony.Carriers.CURRENT + " INTEGER," + 80 Telephony.Carriers.PROTOCOL + " TEXT," + 81 Telephony.Carriers.ROAMING_PROTOCOL + " TEXT," + 82 Telephony.Carriers.CARRIER_ENABLED + " BOOLEAN," + 83 Telephony.Carriers.BEARER + " INTEGER," + 84 Telephony.Carriers.MVNO_TYPE + " TEXT," + 85 Telephony.Carriers.MVNO_MATCH_DATA + " TEXT," + 86 Telephony.Carriers.SUBSCRIPTION_ID + " INTEGER DEFAULT " + 87 ParticipantData.DEFAULT_SELF_SUB_ID + ");"; 88 89 public static final String[] APN_PROJECTION = { 90 Telephony.Carriers.TYPE, // 0 91 Telephony.Carriers.MMSC, // 1 92 Telephony.Carriers.MMSPROXY, // 2 93 Telephony.Carriers.MMSPORT, // 3 94 Telephony.Carriers._ID, // 4 95 Telephony.Carriers.CURRENT, // 5 96 Telephony.Carriers.NUMERIC, // 6 97 Telephony.Carriers.NAME, // 7 98 Telephony.Carriers.MCC, // 8 99 Telephony.Carriers.MNC, // 9 100 Telephony.Carriers.APN, // 10 101 Telephony.Carriers.SUBSCRIPTION_ID // 11 102 }; 103 104 public static final int COLUMN_TYPE = 0; 105 public static final int COLUMN_MMSC = 1; 106 public static final int COLUMN_MMSPROXY = 2; 107 public static final int COLUMN_MMSPORT = 3; 108 public static final int COLUMN_ID = 4; 109 public static final int COLUMN_CURRENT = 5; 110 public static final int COLUMN_NUMERIC = 6; 111 public static final int COLUMN_NAME = 7; 112 public static final int COLUMN_MCC = 8; 113 public static final int COLUMN_MNC = 9; 114 public static final int COLUMN_APN = 10; 115 public static final int COLUMN_SUB_ID = 11; 116 117 public static final String[] APN_FULL_PROJECTION = { 118 Telephony.Carriers.NAME, 119 Telephony.Carriers.MCC, 120 Telephony.Carriers.MNC, 121 Telephony.Carriers.APN, 122 Telephony.Carriers.USER, 123 Telephony.Carriers.SERVER, 124 Telephony.Carriers.PASSWORD, 125 Telephony.Carriers.PROXY, 126 Telephony.Carriers.PORT, 127 Telephony.Carriers.MMSC, 128 Telephony.Carriers.MMSPROXY, 129 Telephony.Carriers.MMSPORT, 130 Telephony.Carriers.AUTH_TYPE, 131 Telephony.Carriers.TYPE, 132 Telephony.Carriers.PROTOCOL, 133 Telephony.Carriers.ROAMING_PROTOCOL, 134 Telephony.Carriers.CARRIER_ENABLED, 135 Telephony.Carriers.BEARER, 136 Telephony.Carriers.MVNO_TYPE, 137 Telephony.Carriers.MVNO_MATCH_DATA, 138 Telephony.Carriers.CURRENT, 139 Telephony.Carriers.SUBSCRIPTION_ID, 140 }; 141 142 private static final String CURRENT_SELECTION = Telephony.Carriers.CURRENT + " NOT NULL"; 143 144 /** 145 * ApnDatabase is initialized asynchronously from the application.onCreate 146 * To ensure that it works in a testing environment it needs to never access the factory context 147 */ 148 public static void initializeAppContext(final Context context) { 149 sContext = context; 150 } 151 152 private ApnDatabase() { 153 super(sContext, APN_DATABASE_NAME, null, DB_VERSION); 154 if (DEBUG) { 155 LogUtil.d(TAG, "ApnDatabase constructor"); 156 } 157 } 158 159 public static ApnDatabase getApnDatabase() { 160 if (sApnDatabase == null) { 161 sApnDatabase = new ApnDatabase(); 162 } 163 return sApnDatabase; 164 } 165 166 public static boolean doesDatabaseExist() { 167 final File dbFile = sContext.getDatabasePath(APN_DATABASE_NAME); 168 return dbFile.exists(); 169 } 170 171 @Override 172 public void onCreate(final SQLiteDatabase db) { 173 if (DEBUG) { 174 LogUtil.d(TAG, "ApnDatabase onCreate"); 175 } 176 // Build the table using defaults (apn info bundled with the app) 177 rebuildTables(db); 178 } 179 180 /** 181 * Get a copy of user changes in the old table 182 * 183 * @return The list of user changed apns 184 */ 185 public static List<ContentValues> loadUserDataFromOldTable(final SQLiteDatabase db) { 186 Cursor cursor = null; 187 try { 188 cursor = db.query(APN_TABLE, 189 APN_FULL_PROJECTION, CURRENT_SELECTION, 190 null/*selectionArgs*/, 191 null/*groupBy*/, null/*having*/, null/*orderBy*/); 192 if (cursor != null) { 193 final List<ContentValues> result = Lists.newArrayList(); 194 while (cursor.moveToNext()) { 195 final ContentValues row = cursorToValues(cursor); 196 if (row != null) { 197 result.add(row); 198 } 199 } 200 return result; 201 } 202 } catch (final SQLiteException e) { 203 LogUtil.w(TAG, "ApnDatabase.loadUserDataFromOldTable: no old user data: " + e, e); 204 } finally { 205 if (cursor != null) { 206 cursor.close(); 207 } 208 } 209 return null; 210 } 211 212 private static final String[] ID_PROJECTION = new String[]{Telephony.Carriers._ID}; 213 214 private static final String ID_SELECTION = Telephony.Carriers._ID + "=?"; 215 216 /** 217 * Store use changes of old table into the new apn table 218 * 219 * @param data The user changes 220 */ 221 public static void saveUserDataFromOldTable( 222 final SQLiteDatabase db, final List<ContentValues> data) { 223 if (data == null || data.size() < 1) { 224 return; 225 } 226 for (final ContentValues row : data) { 227 // Build query from the row data. It is an exact match, column by column, 228 // except the CURRENT column 229 final StringBuilder selectionBuilder = new StringBuilder(); 230 final ArrayList<String> selectionArgs = Lists.newArrayList(); 231 for (final String key : row.keySet()) { 232 if (!Telephony.Carriers.CURRENT.equals(key)) { 233 if (selectionBuilder.length() > 0) { 234 selectionBuilder.append(" AND "); 235 } 236 final String value = row.getAsString(key); 237 if (TextUtils.isEmpty(value)) { 238 selectionBuilder.append(key).append(" IS NULL"); 239 } else { 240 selectionBuilder.append(key).append("=?"); 241 selectionArgs.add(value); 242 } 243 } 244 } 245 Cursor cursor = null; 246 try { 247 cursor = db.query(APN_TABLE, 248 ID_PROJECTION, 249 selectionBuilder.toString(), 250 selectionArgs.toArray(new String[0]), 251 null/*groupBy*/, null/*having*/, null/*orderBy*/); 252 if (cursor != null && cursor.moveToFirst()) { 253 db.update(APN_TABLE, row, ID_SELECTION, new String[]{cursor.getString(0)}); 254 } else { 255 // User APN does not exist, insert into the new table 256 row.put(Telephony.Carriers.NUMERIC, 257 PhoneUtils.canonicalizeMccMnc( 258 row.getAsString(Telephony.Carriers.MCC), 259 row.getAsString(Telephony.Carriers.MNC)) 260 ); 261 db.insert(APN_TABLE, null/*nullColumnHack*/, row); 262 } 263 } catch (final SQLiteException e) { 264 LogUtil.e(TAG, "ApnDatabase.saveUserDataFromOldTable: query error " + e, e); 265 } finally { 266 if (cursor != null) { 267 cursor.close(); 268 } 269 } 270 } 271 } 272 273 // Convert Cursor to ContentValues 274 private static ContentValues cursorToValues(final Cursor cursor) { 275 final int columnCount = cursor.getColumnCount(); 276 if (columnCount > 0) { 277 final ContentValues result = new ContentValues(); 278 for (int i = 0; i < columnCount; i++) { 279 final String name = cursor.getColumnName(i); 280 final String value = cursor.getString(i); 281 result.put(name, value); 282 } 283 return result; 284 } 285 return null; 286 } 287 288 @Override 289 public void onOpen(final SQLiteDatabase db) { 290 super.onOpen(db); 291 if (DEBUG) { 292 LogUtil.d(TAG, "ApnDatabase onOpen"); 293 } 294 } 295 296 @Override 297 public void close() { 298 super.close(); 299 if (DEBUG) { 300 LogUtil.d(TAG, "ApnDatabase close"); 301 } 302 } 303 304 private void rebuildTables(final SQLiteDatabase db) { 305 if (DEBUG) { 306 LogUtil.d(TAG, "ApnDatabase rebuildTables"); 307 } 308 db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE + ";"); 309 db.execSQL(APN_TABLE_SQL); 310 loadApnTable(db); 311 } 312 313 @Override 314 public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { 315 if (DEBUG) { 316 LogUtil.d(TAG, "ApnDatabase onUpgrade"); 317 } 318 rebuildTables(db); 319 } 320 321 @Override 322 public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { 323 if (DEBUG) { 324 LogUtil.d(TAG, "ApnDatabase onDowngrade"); 325 } 326 rebuildTables(db); 327 } 328 329 /** 330 * Load APN table from app resources 331 */ 332 private static void loadApnTable(final SQLiteDatabase db) { 333 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 334 LogUtil.v(TAG, "ApnDatabase loadApnTable"); 335 } 336 final Resources r = sContext.getResources(); 337 final XmlResourceParser parser = r.getXml(R.xml.apns); 338 final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser); 339 processor.setApnHandler(new ApnsXmlProcessor.ApnHandler() { 340 @Override 341 public void process(final ContentValues apnValues) { 342 db.insert(APN_TABLE, null/*nullColumnHack*/, apnValues); 343 } 344 }); 345 try { 346 processor.process(); 347 } catch (final Exception e) { 348 Log.e(TAG, "Got exception while loading APN database.", e); 349 } finally { 350 parser.close(); 351 } 352 } 353 354 public static void forceBuildAndLoadApnTables() { 355 final SQLiteDatabase db = getApnDatabase().getWritableDatabase(); 356 db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE); 357 // Table(s) always need for JB MR1 for APN support for MMS because JB MR1 throws 358 // a SecurityException when trying to access the carriers table (which holds the 359 // APNs). Some JB MR2 devices also throw the security exception, so we're building 360 // the table for JB MR2, too. 361 db.execSQL(APN_TABLE_SQL); 362 363 loadApnTable(db); 364 } 365 366 /** 367 * Clear all tables 368 */ 369 public static void clearTables() { 370 final SQLiteDatabase db = getApnDatabase().getWritableDatabase(); 371 db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE); 372 db.execSQL(APN_TABLE_SQL); 373 } 374 } 375