Home | History | Annotate | Download | only in sms
      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