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      */
initializeAppContext(final Context context)148     public static void initializeAppContext(final Context context) {
149         sContext = context;
150     }
151 
ApnDatabase()152     private ApnDatabase() {
153         super(sContext, APN_DATABASE_NAME, null, DB_VERSION);
154         if (DEBUG) {
155             LogUtil.d(TAG, "ApnDatabase constructor");
156         }
157     }
158 
getApnDatabase()159     public static ApnDatabase getApnDatabase() {
160         if (sApnDatabase == null) {
161             sApnDatabase = new ApnDatabase();
162         }
163         return sApnDatabase;
164     }
165 
doesDatabaseExist()166     public static boolean doesDatabaseExist() {
167         final File dbFile = sContext.getDatabasePath(APN_DATABASE_NAME);
168         return dbFile.exists();
169     }
170 
171     @Override
onCreate(final SQLiteDatabase db)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      */
loadUserDataFromOldTable(final SQLiteDatabase db)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      */
saveUserDataFromOldTable( final SQLiteDatabase db, final List<ContentValues> data)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
cursorToValues(final Cursor cursor)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
onOpen(final SQLiteDatabase db)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
close()297     public void close() {
298         super.close();
299         if (DEBUG) {
300             LogUtil.d(TAG, "ApnDatabase close");
301         }
302     }
303 
rebuildTables(final SQLiteDatabase db)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
onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion)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
onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion)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      */
loadApnTable(final SQLiteDatabase db)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 
forceBuildAndLoadApnTables()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      */
clearTables()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