ApnDatabase.java revision d3b009ae55651f1e60950342468e3c37fdeb0796
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
17package com.android.messaging.sms;
18
19import android.content.ContentValues;
20import android.content.Context;
21import android.content.res.Resources;
22import android.content.res.XmlResourceParser;
23import android.database.Cursor;
24import android.database.sqlite.SQLiteDatabase;
25import android.database.sqlite.SQLiteException;
26import android.database.sqlite.SQLiteOpenHelper;
27import android.provider.Telephony;
28import android.text.TextUtils;
29import android.util.Log;
30
31import com.android.messaging.R;
32import com.android.messaging.datamodel.data.ParticipantData;
33import com.android.messaging.util.LogUtil;
34import com.android.messaging.util.PhoneUtils;
35import com.google.common.collect.Lists;
36
37import java.io.File;
38import java.util.ArrayList;
39import 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 */
45public 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