TelephonyProvider.java revision 6de10270199e49235eb381f8f478d4b7a1c72fbe
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.ContentProvider;
21import android.content.ContentUris;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.SharedPreferences;
25import android.content.UriMatcher;
26import android.content.res.Resources;
27import android.content.res.XmlResourceParser;
28import android.database.Cursor;
29import android.database.SQLException;
30import android.database.sqlite.SQLiteDatabase;
31import android.database.sqlite.SQLiteException;
32import android.database.sqlite.SQLiteOpenHelper;
33import android.database.sqlite.SQLiteQueryBuilder;
34import android.net.Uri;
35import android.os.Environment;
36import android.provider.Telephony;
37import android.telephony.SubscriptionManager;
38import android.telephony.TelephonyManager;
39import android.util.Log;
40import android.util.Xml;
41
42import com.android.internal.telephony.BaseCommands;
43import com.android.internal.telephony.Phone;
44import com.android.internal.telephony.PhoneConstants;
45import com.android.internal.util.XmlUtils;
46
47import org.xmlpull.v1.XmlPullParser;
48import org.xmlpull.v1.XmlPullParserException;
49
50import java.io.File;
51import java.io.FileNotFoundException;
52import java.io.FileReader;
53import java.io.IOException;
54
55
56public class TelephonyProvider extends ContentProvider
57{
58    private static final String DATABASE_NAME = "telephony.db";
59    private static final boolean DBG = true;
60    private static final boolean VDBG = false;
61
62    private static final int DATABASE_VERSION = 9 << 16;
63    private static final int URL_UNKNOWN = 0;
64    private static final int URL_TELEPHONY = 1;
65    private static final int URL_CURRENT = 2;
66    private static final int URL_ID = 3;
67    private static final int URL_RESTOREAPN = 4;
68    private static final int URL_PREFERAPN = 5;
69    private static final int URL_PREFERAPN_NO_UPDATE = 6;
70    private static final int URL_SIMINFO = 7;
71    private static final int URL_TELEPHONY_USING_SUBID = 8;
72    private static final int URL_CURRENT_USING_SUBID = 9;
73    private static final int URL_RESTOREAPN_USING_SUBID = 10;
74    private static final int URL_PREFERAPN_USING_SUBID = 11;
75    private static final int URL_PREFERAPN_NO_UPDATE_USING_SUBID = 12;
76    private static final int URL_SIMINFO_USING_SUBID = 13;
77
78    private static final String TAG = "TelephonyProvider";
79    private static final String CARRIERS_TABLE = "carriers";
80    private static final String SIMINFO_TABLE = "siminfo";
81
82    private static final String PREF_FILE = "preferred-apn";
83    private static final String COLUMN_APN_ID = "apn_id";
84
85    private static final String PARTNER_APNS_PATH = "etc/apns-conf.xml";
86
87    private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
88
89    private static final ContentValues s_currentNullMap;
90    private static final ContentValues s_currentSetMap;
91
92    static {
93        s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
94        s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);
95        s_urlMatcher.addURI("telephony", "carriers/#", URL_ID);
96        s_urlMatcher.addURI("telephony", "carriers/restore", URL_RESTOREAPN);
97        s_urlMatcher.addURI("telephony", "carriers/preferapn", URL_PREFERAPN);
98        s_urlMatcher.addURI("telephony", "carriers/preferapn_no_update", URL_PREFERAPN_NO_UPDATE);
99
100        s_urlMatcher.addURI("telephony", "siminfo", URL_SIMINFO);
101
102        s_urlMatcher.addURI("telephony", "carriers/subId/*", URL_TELEPHONY_USING_SUBID);
103        s_urlMatcher.addURI("telephony", "carriers/current/subId/*", URL_CURRENT_USING_SUBID);
104        s_urlMatcher.addURI("telephony", "carriers/restore/subId/*", URL_RESTOREAPN_USING_SUBID);
105        s_urlMatcher.addURI("telephony", "carriers/preferapn/subId/*", URL_PREFERAPN_USING_SUBID);
106        s_urlMatcher.addURI("telephony", "carriers/preferapn_no_update/subId/*",
107                URL_PREFERAPN_NO_UPDATE_USING_SUBID);
108
109
110        s_currentNullMap = new ContentValues(1);
111        s_currentNullMap.put("current", (Long) null);
112
113        s_currentSetMap = new ContentValues(1);
114        s_currentSetMap.put("current", "1");
115    }
116
117    private static class DatabaseHelper extends SQLiteOpenHelper {
118        // Context to access resources with
119        private Context mContext;
120
121        /**
122         * DatabaseHelper helper class for loading apns into a database.
123         *
124         * @param context of the user.
125         */
126        public DatabaseHelper(Context context) {
127            super(context, DATABASE_NAME, null, getVersion(context));
128            mContext = context;
129        }
130
131        private static int getVersion(Context context) {
132            if (VDBG) log("getVersion:+");
133            // Get the database version, combining a static schema version and the XML version
134            Resources r = context.getResources();
135            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
136            try {
137                XmlUtils.beginDocument(parser, "apns");
138                int publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
139                int version = DATABASE_VERSION | publicversion;
140                if (VDBG) log("getVersion:- version=0x" + Integer.toHexString(version));
141                return version;
142            } catch (Exception e) {
143                loge("Can't get version of APN database" + e + " return version=" +
144                        Integer.toHexString(DATABASE_VERSION));
145                return DATABASE_VERSION;
146            } finally {
147                parser.close();
148            }
149        }
150
151        @Override
152        public void onCreate(SQLiteDatabase db) {
153            if (DBG) log("dbh.onCreate:+ db=" + db);
154            createSimInfoTable(db);
155            createCarriersTable(db);
156            initDatabase(db);
157            if (DBG) log("dbh.onCreate:- db=" + db);
158        }
159
160        @Override
161        public void onOpen(SQLiteDatabase db) {
162            if (VDBG) log("dbh.onOpen:+ db=" + db);
163            try {
164                // Try to access the table and create it if "no such table"
165                db.query(SIMINFO_TABLE, null, null, null, null, null, null);
166                if (DBG) log("dbh.onOpen: ok, queried table=" + SIMINFO_TABLE);
167            } catch (SQLiteException e) {
168                loge("Exception " + SIMINFO_TABLE + "e=" + e);
169                if (e.getMessage().startsWith("no such table")) {
170                    createSimInfoTable(db);
171                }
172            }
173            try {
174                db.query(CARRIERS_TABLE, null, null, null, null, null, null);
175                if (DBG) log("dbh.onOpen: ok, queried table=" + CARRIERS_TABLE);
176            } catch (SQLiteException e) {
177                loge("Exception " + CARRIERS_TABLE + " e=" + e);
178                if (e.getMessage().startsWith("no such table")) {
179                    createCarriersTable(db);
180                }
181            }
182            if (VDBG) log("dbh.onOpen:- db=" + db);
183        }
184
185        private void createSimInfoTable(SQLiteDatabase db) {
186            if (DBG) log("dbh.createSimInfoTable:+");
187            db.execSQL("CREATE TABLE " + SIMINFO_TABLE + "("
188                    + "_id INTEGER PRIMARY KEY AUTOINCREMENT,"
189                    + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
190                    + SubscriptionManager.SIM_ID + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
191                    + SubscriptionManager.DISPLAY_NAME + " TEXT,"
192                    + SubscriptionManager.NAME_SOURCE + " INTEGER DEFAULT " + SubscriptionManager.DEFAULT_SOURCE + ","
193                    + SubscriptionManager.COLOR + " INTEGER DEFAULT " + SubscriptionManager.COLOR_DEFAULT + ","
194                    + SubscriptionManager.NUMBER + " TEXT,"
195                    + SubscriptionManager.DISPLAY_NUMBER_FORMAT + " INTEGER NOT NULL DEFAULT " + SubscriptionManager.DISLPAY_NUMBER_DEFAULT + ","
196                    + SubscriptionManager.DATA_ROAMING + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT
197                    + ");");
198            if (DBG) log("dbh.createSimInfoTable:-");
199        }
200
201        private void createCarriersTable(SQLiteDatabase db) {
202            // Set up the database schema
203            if (DBG) log("dbh.createCarriersTable:+");
204            db.execSQL("CREATE TABLE " + CARRIERS_TABLE +
205                "(_id INTEGER PRIMARY KEY," +
206                    "name TEXT," +
207                    "numeric TEXT," +
208                    "mcc TEXT," +
209                    "mnc TEXT," +
210                    "apn TEXT," +
211                    "user TEXT," +
212                    "server TEXT," +
213                    "password TEXT," +
214                    "proxy TEXT," +
215                    "port TEXT," +
216                    "mmsproxy TEXT," +
217                    "mmsport TEXT," +
218                    "mmsc TEXT," +
219                    "authtype INTEGER," +
220                    "type TEXT," +
221                    "current INTEGER," +
222                    "protocol TEXT," +
223                    "roaming_protocol TEXT," +
224                    "carrier_enabled BOOLEAN," +
225                    "bearer INTEGER," +
226                    "mvno_type TEXT," +
227                    "mvno_match_data TEXT," +
228                    "sub_id LONG DEFAULT -1);");
229             /* FIXME Currenlty sub_id is column is not used for query purpose.
230             This would be modified to more appropriate default value later. */
231            if (DBG) log("dbh.createCarriersTable:-");
232        }
233        private void initDatabase(SQLiteDatabase db) {
234            if (VDBG) log("dbh.initDatabase:+ db=" + db);
235            // Read internal APNS data
236            Resources r = mContext.getResources();
237            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
238            int publicversion = -1;
239            try {
240                XmlUtils.beginDocument(parser, "apns");
241                publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
242                loadApns(db, parser);
243            } catch (Exception e) {
244                loge("Got exception while loading APN database." + e);
245            } finally {
246                parser.close();
247            }
248
249            // Read external APNS data (partner-provided)
250            XmlPullParser confparser = null;
251            // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
252            File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
253            FileReader confreader = null;
254            try {
255                confreader = new FileReader(confFile);
256                confparser = Xml.newPullParser();
257                confparser.setInput(confreader);
258                XmlUtils.beginDocument(confparser, "apns");
259
260                // Sanity check. Force internal version and confidential versions to agree
261                int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
262                if (publicversion != confversion) {
263                    throw new IllegalStateException("Internal APNS file version doesn't match "
264                            + confFile.getAbsolutePath());
265                }
266
267                loadApns(db, confparser);
268            } catch (FileNotFoundException e) {
269                // It's ok if the file isn't found. It means there isn't a confidential file
270                // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
271            } catch (Exception e) {
272                loge("Exception while parsing '" + confFile.getAbsolutePath() + "'" + e);
273            } finally {
274                try { if (confreader != null) confreader.close(); } catch (IOException e) { }
275            }
276            if (VDBG) log("dbh.initDatabase:- db=" + db);
277        }
278
279        @Override
280        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
281            if (DBG) {
282                log("dbh.onUpgrade:+ db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
283            }
284
285            if (oldVersion < (5 << 16 | 6)) {
286                // 5 << 16 is the Database version and 6 in the xml version.
287
288                // This change adds a new authtype column to the database.
289                // The auth type column can have 4 values: 0 (None), 1 (PAP), 2 (CHAP)
290                // 3 (PAP or CHAP). To avoid breaking compatibility, with already working
291                // APNs, the unset value (-1) will be used. If the value is -1.
292                // the authentication will default to 0 (if no user / password) is specified
293                // or to 3. Currently, there have been no reported problems with
294                // pre-configured APNs and hence it is set to -1 for them. Similarly,
295                // if the user, has added a new APN, we set the authentication type
296                // to -1.
297
298                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
299                        " ADD COLUMN authtype INTEGER DEFAULT -1;");
300
301                oldVersion = 5 << 16 | 6;
302            }
303            if (oldVersion < (6 << 16 | 6)) {
304                // Add protcol fields to the APN. The XML file does not change.
305                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
306                        " ADD COLUMN protocol TEXT DEFAULT IP;");
307                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
308                        " ADD COLUMN roaming_protocol TEXT DEFAULT IP;");
309                oldVersion = 6 << 16 | 6;
310            }
311            if (oldVersion < (7 << 16 | 6)) {
312                // Add carrier_enabled, bearer fields to the APN. The XML file does not change.
313                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
314                        " ADD COLUMN carrier_enabled BOOLEAN DEFAULT 1;");
315                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
316                        " ADD COLUMN bearer INTEGER DEFAULT 0;");
317                oldVersion = 7 << 16 | 6;
318            }
319            if (oldVersion < (8 << 16 | 6)) {
320                // Add mvno_type, mvno_match_data fields to the APN.
321                // The XML file does not change.
322                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
323                        " ADD COLUMN mvno_type TEXT DEFAULT '';");
324                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
325                        " ADD COLUMN mvno_match_data TEXT DEFAULT '';");
326                oldVersion = 8 << 16 | 6;
327            }
328            if (oldVersion < (9 << 16 | 6)) {
329                db.execSQL("ALTER TABLE " + CARRIERS_TABLE +
330                        " ADD COLUMN sub_id LONG DEFAULT -1;");
331                oldVersion = 9 << 16 | 6;
332            }
333            if (DBG) {
334                log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
335            }
336        }
337
338        /**
339         * Gets the next row of apn values.
340         *
341         * @param parser the parser
342         * @return the row or null if it's not an apn
343         */
344        private ContentValues getRow(XmlPullParser parser) {
345            if (!"apn".equals(parser.getName())) {
346                return null;
347            }
348
349            ContentValues map = new ContentValues();
350
351            String mcc = parser.getAttributeValue(null, "mcc");
352            String mnc = parser.getAttributeValue(null, "mnc");
353            String numeric = mcc + mnc;
354
355            map.put(Telephony.Carriers.NUMERIC,numeric);
356            map.put(Telephony.Carriers.MCC, mcc);
357            map.put(Telephony.Carriers.MNC, mnc);
358            map.put(Telephony.Carriers.NAME, parser.getAttributeValue(null, "carrier"));
359            map.put(Telephony.Carriers.APN, parser.getAttributeValue(null, "apn"));
360            map.put(Telephony.Carriers.USER, parser.getAttributeValue(null, "user"));
361            map.put(Telephony.Carriers.SERVER, parser.getAttributeValue(null, "server"));
362            map.put(Telephony.Carriers.PASSWORD, parser.getAttributeValue(null, "password"));
363
364            // do not add NULL to the map so that insert() will set the default value
365            String proxy = parser.getAttributeValue(null, "proxy");
366            if (proxy != null) {
367                map.put(Telephony.Carriers.PROXY, proxy);
368            }
369            String port = parser.getAttributeValue(null, "port");
370            if (port != null) {
371                map.put(Telephony.Carriers.PORT, port);
372            }
373            String mmsproxy = parser.getAttributeValue(null, "mmsproxy");
374            if (mmsproxy != null) {
375                map.put(Telephony.Carriers.MMSPROXY, mmsproxy);
376            }
377            String mmsport = parser.getAttributeValue(null, "mmsport");
378            if (mmsport != null) {
379                map.put(Telephony.Carriers.MMSPORT, mmsport);
380            }
381            map.put(Telephony.Carriers.MMSC, parser.getAttributeValue(null, "mmsc"));
382            String type = parser.getAttributeValue(null, "type");
383            if (type != null) {
384                map.put(Telephony.Carriers.TYPE, type);
385            }
386
387            String auth = parser.getAttributeValue(null, "authtype");
388            if (auth != null) {
389                map.put(Telephony.Carriers.AUTH_TYPE, Integer.parseInt(auth));
390            }
391
392            String protocol = parser.getAttributeValue(null, "protocol");
393            if (protocol != null) {
394                map.put(Telephony.Carriers.PROTOCOL, protocol);
395            }
396
397            String roamingProtocol = parser.getAttributeValue(null, "roaming_protocol");
398            if (roamingProtocol != null) {
399                map.put(Telephony.Carriers.ROAMING_PROTOCOL, roamingProtocol);
400            }
401
402            String carrierEnabled = parser.getAttributeValue(null, "carrier_enabled");
403            if (carrierEnabled != null) {
404                map.put(Telephony.Carriers.CARRIER_ENABLED, Boolean.parseBoolean(carrierEnabled));
405            }
406
407            String bearer = parser.getAttributeValue(null, "bearer");
408            if (bearer != null) {
409                map.put(Telephony.Carriers.BEARER, Integer.parseInt(bearer));
410            }
411
412            String mvno_type = parser.getAttributeValue(null, "mvno_type");
413            if (mvno_type != null) {
414                String mvno_match_data = parser.getAttributeValue(null, "mvno_match_data");
415                if (mvno_match_data != null) {
416                    map.put(Telephony.Carriers.MVNO_TYPE, mvno_type);
417                    map.put(Telephony.Carriers.MVNO_MATCH_DATA, mvno_match_data);
418                }
419            }
420            return map;
421        }
422
423        /*
424         * Loads apns from xml file into the database
425         *
426         * @param db the sqlite database to write to
427         * @param parser the xml parser
428         *
429         */
430        private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
431            if (parser != null) {
432                try {
433                    db.beginTransaction();
434                    XmlUtils.nextElement(parser);
435                    while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
436                        ContentValues row = getRow(parser);
437                        if (row == null) {
438                            throw new XmlPullParserException("Expected 'apn' tag", parser, null);
439                        }
440                        insertAddingDefaults(db, CARRIERS_TABLE, row);
441                        XmlUtils.nextElement(parser);
442                    }
443                    db.setTransactionSuccessful();
444                } catch (XmlPullParserException e) {
445                    loge("Got XmlPullParserException while loading apns." + e);
446                } catch (IOException e) {
447                    loge("Got IOException while loading apns." + e);
448                } catch (SQLException e) {
449                    loge("Got SQLException while loading apns." + e);
450                } finally {
451                    db.endTransaction();
452                }
453            }
454        }
455
456        private void insertAddingDefaults(SQLiteDatabase db, String table, ContentValues row) {
457            // Initialize defaults if any
458            if (row.containsKey(Telephony.Carriers.AUTH_TYPE) == false) {
459                row.put(Telephony.Carriers.AUTH_TYPE, -1);
460            }
461            if (row.containsKey(Telephony.Carriers.PROTOCOL) == false) {
462                row.put(Telephony.Carriers.PROTOCOL, "IP");
463            }
464            if (row.containsKey(Telephony.Carriers.ROAMING_PROTOCOL) == false) {
465                row.put(Telephony.Carriers.ROAMING_PROTOCOL, "IP");
466            }
467            if (row.containsKey(Telephony.Carriers.CARRIER_ENABLED) == false) {
468                row.put(Telephony.Carriers.CARRIER_ENABLED, true);
469            }
470            if (row.containsKey(Telephony.Carriers.BEARER) == false) {
471                row.put(Telephony.Carriers.BEARER, 0);
472            }
473            if (row.containsKey(Telephony.Carriers.MVNO_TYPE) == false) {
474                row.put(Telephony.Carriers.MVNO_TYPE, "");
475            }
476            if (row.containsKey(Telephony.Carriers.MVNO_MATCH_DATA) == false) {
477                row.put(Telephony.Carriers.MVNO_MATCH_DATA, "");
478            }
479            db.insert(CARRIERS_TABLE, null, row);
480        }
481    }
482
483    @Override
484    public boolean onCreate() {
485        if (VDBG) log("onCreate:+");
486        mOpenHelper = new DatabaseHelper(getContext());
487        if (VDBG) log("onCreate:- ret true");
488        return true;
489    }
490
491    private void setPreferredApnId(Long id, long subId) {
492        SharedPreferences sp = getContext().getSharedPreferences(
493                PREF_FILE + subId, Context.MODE_PRIVATE);
494        SharedPreferences.Editor editor = sp.edit();
495        editor.putLong(COLUMN_APN_ID, id != null ? id.longValue() : -1);
496        editor.apply();
497    }
498
499    private long getPreferredApnId(long subId) {
500        SharedPreferences sp = getContext().getSharedPreferences(
501                PREF_FILE + subId, Context.MODE_PRIVATE);
502        return sp.getLong(COLUMN_APN_ID, -1);
503    }
504
505    @Override
506    public Cursor query(Uri url, String[] projectionIn, String selection,
507            String[] selectionArgs, String sort) {
508        TelephonyManager mTelephonyManager =
509                (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
510        long subId = SubscriptionManager.getDefaultSubId();
511        String subIdString;
512        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
513        qb.setStrict(true); // a little protection from injection attacks
514        qb.setTables("carriers");
515
516        int match = s_urlMatcher.match(url);
517        switch (match) {
518            case URL_TELEPHONY_USING_SUBID: {
519                subIdString = url.getLastPathSegment();
520                try {
521                    subId = Long.parseLong(subIdString);
522                } catch (NumberFormatException e) {
523                    loge("NumberFormatException" + e);
524                    return null;
525                }
526                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
527                qb.appendWhere("numeric = " + mTelephonyManager.getSimOperator(subId));
528                // FIXME alter the selection to pass subId
529                // selection = selection + "and subId = "
530            }
531            //intentional fall through from above case
532            // do nothing
533            case URL_TELEPHONY: {
534                break;
535            }
536
537            case URL_CURRENT_USING_SUBID: {
538                subIdString = url.getLastPathSegment();
539                try {
540                    subId = Long.parseLong(subIdString);
541                } catch (NumberFormatException e) {
542                    loge("NumberFormatException" + e);
543                    return null;
544                }
545                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
546                // FIXME alter the selection to pass subId
547                // selection = selection + "and subId = "
548            }
549            //intentional fall through from above case
550            case URL_CURRENT: {
551                qb.appendWhere("current IS NOT NULL");
552                // do not ignore the selection since MMS may use it.
553                //selection = null;
554                break;
555            }
556
557            case URL_ID: {
558                qb.appendWhere("_id = " + url.getPathSegments().get(1));
559                break;
560            }
561
562            case URL_PREFERAPN_USING_SUBID:
563            case URL_PREFERAPN_NO_UPDATE_USING_SUBID: {
564                subIdString = url.getLastPathSegment();
565                try {
566                    subId = Long.parseLong(subIdString);
567                } catch (NumberFormatException e) {
568                    loge("NumberFormatException" + e);
569                    return null;
570                }
571                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
572            }
573            //intentional fall through from above case
574            case URL_PREFERAPN:
575            case URL_PREFERAPN_NO_UPDATE: {
576                qb.appendWhere("_id = " + getPreferredApnId(subId));
577                break;
578            }
579
580            case URL_SIMINFO: {
581                qb.setTables(SIMINFO_TABLE);
582                break;
583            }
584
585            default: {
586                return null;
587            }
588        }
589
590        if (match != URL_SIMINFO) {
591            if (projectionIn != null) {
592                for (String column : projectionIn) {
593                    if (Telephony.Carriers.TYPE.equals(column) ||
594                            Telephony.Carriers.MMSC.equals(column) ||
595                            Telephony.Carriers.MMSPROXY.equals(column) ||
596                            Telephony.Carriers.MMSPORT.equals(column) ||
597                            Telephony.Carriers.APN.equals(column)) {
598                        // noop
599                    } else {
600                        checkPermission();
601                        break;
602                    }
603                }
604            } else {
605                // null returns all columns, so need permission check
606                checkPermission();
607            }
608        }
609
610        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
611        Cursor ret = null;
612        try {
613            ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort);
614        } catch (SQLException e) {
615            loge("got exception when querying: " + e);
616        }
617        if (ret != null)
618            ret.setNotificationUri(getContext().getContentResolver(), url);
619        return ret;
620    }
621
622    @Override
623    public String getType(Uri url)
624    {
625        switch (s_urlMatcher.match(url)) {
626        case URL_TELEPHONY:
627        case URL_TELEPHONY_USING_SUBID:
628            return "vnd.android.cursor.dir/telephony-carrier";
629
630        case URL_ID:
631            return "vnd.android.cursor.item/telephony-carrier";
632
633        case URL_PREFERAPN_USING_SUBID:
634        case URL_PREFERAPN_NO_UPDATE_USING_SUBID:
635        case URL_PREFERAPN:
636        case URL_PREFERAPN_NO_UPDATE:
637            return "vnd.android.cursor.item/telephony-carrier";
638
639        default:
640            throw new IllegalArgumentException("Unknown URL " + url);
641        }
642    }
643
644    @Override
645    public Uri insert(Uri url, ContentValues initialValues)
646    {
647        Uri result = null;
648        long subId = SubscriptionManager.getDefaultSubId();
649
650        checkPermission();
651
652        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
653        int match = s_urlMatcher.match(url);
654        boolean notify = false;
655        switch (match)
656        {
657            case URL_TELEPHONY_USING_SUBID:
658            {
659                String subIdString = url.getLastPathSegment();
660                try {
661                    subId = Long.parseLong(subIdString);
662                } catch (NumberFormatException e) {
663                    loge("NumberFormatException" + e);
664                    return result;
665                }
666                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
667            }
668            //intentional fall through from above case
669
670            case URL_TELEPHONY:
671            {
672                ContentValues values;
673                if (initialValues != null) {
674                    values = new ContentValues(initialValues);
675                } else {
676                    values = new ContentValues();
677                }
678
679                // TODO Review this. This code should probably not bet here.
680                // It is valid for the database to return a null string.
681                if (!values.containsKey(Telephony.Carriers.NAME)) {
682                    values.put(Telephony.Carriers.NAME, "");
683                }
684                if (!values.containsKey(Telephony.Carriers.APN)) {
685                    values.put(Telephony.Carriers.APN, "");
686                }
687                if (!values.containsKey(Telephony.Carriers.PORT)) {
688                    values.put(Telephony.Carriers.PORT, "");
689                }
690                if (!values.containsKey(Telephony.Carriers.PROXY)) {
691                    values.put(Telephony.Carriers.PROXY, "");
692                }
693                if (!values.containsKey(Telephony.Carriers.USER)) {
694                    values.put(Telephony.Carriers.USER, "");
695                }
696                if (!values.containsKey(Telephony.Carriers.SERVER)) {
697                    values.put(Telephony.Carriers.SERVER, "");
698                }
699                if (!values.containsKey(Telephony.Carriers.PASSWORD)) {
700                    values.put(Telephony.Carriers.PASSWORD, "");
701                }
702                if (!values.containsKey(Telephony.Carriers.MMSPORT)) {
703                    values.put(Telephony.Carriers.MMSPORT, "");
704                }
705                if (!values.containsKey(Telephony.Carriers.MMSPROXY)) {
706                    values.put(Telephony.Carriers.MMSPROXY, "");
707                }
708                if (!values.containsKey(Telephony.Carriers.AUTH_TYPE)) {
709                    values.put(Telephony.Carriers.AUTH_TYPE, -1);
710                }
711                if (!values.containsKey(Telephony.Carriers.PROTOCOL)) {
712                    values.put(Telephony.Carriers.PROTOCOL, "IP");
713                }
714                if (!values.containsKey(Telephony.Carriers.ROAMING_PROTOCOL)) {
715                    values.put(Telephony.Carriers.ROAMING_PROTOCOL, "IP");
716                }
717                if (!values.containsKey(Telephony.Carriers.CARRIER_ENABLED)) {
718                    values.put(Telephony.Carriers.CARRIER_ENABLED, true);
719                }
720                if (!values.containsKey(Telephony.Carriers.BEARER)) {
721                    values.put(Telephony.Carriers.BEARER, 0);
722                }
723                if (!values.containsKey(Telephony.Carriers.MVNO_TYPE)) {
724                    values.put(Telephony.Carriers.MVNO_TYPE, "");
725                }
726                if (!values.containsKey(Telephony.Carriers.MVNO_MATCH_DATA)) {
727                    values.put(Telephony.Carriers.MVNO_MATCH_DATA, "");
728                }
729
730                if (!values.containsKey(Telephony.Carriers.SUB_ID)) {
731                    values.put(Telephony.Carriers.SUB_ID, subId);
732                }
733
734                long rowID = db.insert(CARRIERS_TABLE, null, values);
735                if (rowID > 0)
736                {
737                    result = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, rowID);
738                    notify = true;
739                }
740
741                if (VDBG) log("inserted " + values.toString() + " rowID = " + rowID);
742                break;
743            }
744
745            case URL_CURRENT_USING_SUBID:
746            {
747                String subIdString = url.getLastPathSegment();
748                try {
749                    subId = Long.parseLong(subIdString);
750                } catch (NumberFormatException e) {
751                    loge("NumberFormatException" + e);
752                    return result;
753                }
754                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
755                // FIXME use subId in the query
756            }
757            //intentional fall through from above case
758
759            case URL_CURRENT:
760            {
761                // null out the previous operator
762                db.update("carriers", s_currentNullMap, "current IS NOT NULL", null);
763
764                String numeric = initialValues.getAsString("numeric");
765                int updated = db.update("carriers", s_currentSetMap,
766                        "numeric = '" + numeric + "'", null);
767
768                if (updated > 0)
769                {
770                    if (VDBG) log("Setting numeric '" + numeric + "' to be the current operator");
771                }
772                else
773                {
774                    loge("Failed setting numeric '" + numeric + "' to the current operator");
775                }
776                break;
777            }
778
779            case URL_PREFERAPN_USING_SUBID:
780            case URL_PREFERAPN_NO_UPDATE_USING_SUBID:
781            {
782                String subIdString = url.getLastPathSegment();
783                try {
784                    subId = Long.parseLong(subIdString);
785                } catch (NumberFormatException e) {
786                    loge("NumberFormatException" + e);
787                    return result;
788                }
789                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
790            }
791            //intentional fall through from above case
792
793            case URL_PREFERAPN:
794            case URL_PREFERAPN_NO_UPDATE:
795            {
796                if (initialValues != null) {
797                    if(initialValues.containsKey(COLUMN_APN_ID)) {
798                        setPreferredApnId(initialValues.getAsLong(COLUMN_APN_ID), subId);
799                    }
800                }
801                break;
802            }
803
804            case URL_SIMINFO: {
805               long id = db.insert(SIMINFO_TABLE, null, initialValues);
806               result = ContentUris.withAppendedId(SubscriptionManager.CONTENT_URI, id);
807               break;
808            }
809        }
810
811        if (notify) {
812            getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
813        }
814
815        return result;
816    }
817
818    @Override
819    public int delete(Uri url, String where, String[] whereArgs)
820    {
821        int count = 0;
822        long subId = SubscriptionManager.getDefaultSubId();
823
824        checkPermission();
825
826        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
827        int match = s_urlMatcher.match(url);
828        switch (match)
829        {
830            case URL_TELEPHONY_USING_SUBID:
831            {
832                 String subIdString = url.getLastPathSegment();
833                 try {
834                     subId = Long.parseLong(subIdString);
835                 } catch (NumberFormatException e) {
836                     loge("NumberFormatException" + e);
837                     throw new IllegalArgumentException("Invalid subId " + url);
838                 }
839                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
840                // FIXME use subId in query
841            }
842            //intentional fall through from above case
843
844            case URL_TELEPHONY:
845            {
846                count = db.delete(CARRIERS_TABLE, where, whereArgs);
847                break;
848            }
849
850            case URL_CURRENT_USING_SUBID: {
851                String subIdString = url.getLastPathSegment();
852                try {
853                    subId = Long.parseLong(subIdString);
854                } catch (NumberFormatException e) {
855                    loge("NumberFormatException" + e);
856                    throw new IllegalArgumentException("Invalid subId " + url);
857                }
858                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
859                // FIXME use subId in query
860            }
861            //intentional fall through from above case
862
863            case URL_CURRENT:
864            {
865                count = db.delete(CARRIERS_TABLE, where, whereArgs);
866                break;
867            }
868
869            case URL_ID:
870            {
871                count = db.delete(CARRIERS_TABLE, Telephony.Carriers._ID + "=?",
872                        new String[] { url.getLastPathSegment() });
873                break;
874            }
875
876            case URL_RESTOREAPN_USING_SUBID: {
877                String subIdString = url.getLastPathSegment();
878                try {
879                    subId = Long.parseLong(subIdString);
880                } catch (NumberFormatException e) {
881                    loge("NumberFormatException" + e);
882                    throw new IllegalArgumentException("Invalid subId " + url);
883                }
884                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
885                // FIXME use subId in query
886            }
887            case URL_RESTOREAPN: {
888                count = 1;
889                restoreDefaultAPN(subId);
890                break;
891            }
892
893            case URL_PREFERAPN_USING_SUBID:
894            case URL_PREFERAPN_NO_UPDATE_USING_SUBID: {
895                String subIdString = url.getLastPathSegment();
896                try {
897                    subId = Long.parseLong(subIdString);
898                } catch (NumberFormatException e) {
899                    loge("NumberFormatException" + e);
900                    throw new IllegalArgumentException("Invalid subId " + url);
901                }
902                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
903            }
904            //intentional fall through from above case
905
906            case URL_PREFERAPN:
907            case URL_PREFERAPN_NO_UPDATE:
908            {
909                setPreferredApnId((long)-1, subId);
910                if ((match == URL_PREFERAPN) || (match == URL_PREFERAPN_USING_SUBID)) count = 1;
911                break;
912            }
913
914            case URL_SIMINFO: {
915                count = db.delete(SIMINFO_TABLE, where, whereArgs);
916                break;
917            }
918
919            default: {
920                throw new UnsupportedOperationException("Cannot delete that URL: " + url);
921            }
922        }
923
924        if (count > 0) {
925            getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
926        }
927
928        return count;
929    }
930
931    @Override
932    public int update(Uri url, ContentValues values, String where, String[] whereArgs)
933    {
934        int count = 0;
935        int uriType = URL_UNKNOWN;
936        long subId = SubscriptionManager.getDefaultSubId();
937
938        checkPermission();
939
940        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
941        int match = s_urlMatcher.match(url);
942        switch (match)
943        {
944            case URL_TELEPHONY_USING_SUBID:
945            {
946                 String subIdString = url.getLastPathSegment();
947                 try {
948                     subId = Long.parseLong(subIdString);
949                 } catch (NumberFormatException e) {
950                     loge("NumberFormatException" + e);
951                     throw new IllegalArgumentException("Invalid subId " + url);
952                 }
953                 if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
954                //FIXME use subId in the query
955            }
956            //intentional fall through from above case
957
958            case URL_TELEPHONY:
959            {
960                count = db.update(CARRIERS_TABLE, values, where, whereArgs);
961                break;
962            }
963
964            case URL_CURRENT_USING_SUBID:
965            {
966                String subIdString = url.getLastPathSegment();
967                try {
968                    subId = Long.parseLong(subIdString);
969                } catch (NumberFormatException e) {
970                    loge("NumberFormatException" + e);
971                    throw new IllegalArgumentException("Invalid subId " + url);
972                }
973                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
974                //FIXME use subId in the query
975            }
976            //intentional fall through from above case
977
978            case URL_CURRENT:
979            {
980                count = db.update(CARRIERS_TABLE, values, where, whereArgs);
981                break;
982            }
983
984            case URL_ID:
985            {
986                if (where != null || whereArgs != null) {
987                    throw new UnsupportedOperationException(
988                            "Cannot update URL " + url + " with a where clause");
989                }
990                count = db.update(CARRIERS_TABLE, values, Telephony.Carriers._ID + "=?",
991                        new String[] { url.getLastPathSegment() });
992                break;
993            }
994
995            case URL_PREFERAPN_USING_SUBID:
996            case URL_PREFERAPN_NO_UPDATE_USING_SUBID:
997            {
998                String subIdString = url.getLastPathSegment();
999                try {
1000                    subId = Long.parseLong(subIdString);
1001                } catch (NumberFormatException e) {
1002                    loge("NumberFormatException" + e);
1003                    throw new IllegalArgumentException("Invalid subId " + url);
1004                }
1005                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
1006            }
1007
1008            case URL_PREFERAPN:
1009            case URL_PREFERAPN_NO_UPDATE:
1010            {
1011                if (values != null) {
1012                    if (values.containsKey(COLUMN_APN_ID)) {
1013                        setPreferredApnId(values.getAsLong(COLUMN_APN_ID), subId);
1014                        if ((match == URL_PREFERAPN) ||
1015                                (match == URL_PREFERAPN_USING_SUBID)) {
1016                            count = 1;
1017                        }
1018                    }
1019                }
1020                break;
1021            }
1022
1023            case URL_SIMINFO: {
1024                count = db.update(SIMINFO_TABLE, values, where, whereArgs);
1025                uriType = URL_SIMINFO;
1026                break;
1027            }
1028
1029            default: {
1030                throw new UnsupportedOperationException("Cannot update that URL: " + url);
1031            }
1032        }
1033
1034        if (count > 0) {
1035            switch (uriType) {
1036                case URL_SIMINFO:
1037                    getContext().getContentResolver().notifyChange(
1038                            SubscriptionManager.CONTENT_URI, null);
1039                    break;
1040                default:
1041                    getContext().getContentResolver().notifyChange(
1042                            Telephony.Carriers.CONTENT_URI, null);
1043            }
1044        }
1045
1046        return count;
1047    }
1048
1049    private void checkPermission() {
1050        getContext().enforceCallingOrSelfPermission("android.permission.WRITE_APN_SETTINGS",
1051                "No permission to write APN settings");
1052    }
1053
1054    private DatabaseHelper mOpenHelper;
1055
1056    private void restoreDefaultAPN(long subId) {
1057        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1058
1059        try {
1060            db.delete(CARRIERS_TABLE, null, null);
1061        } catch (SQLException e) {
1062            loge("got exception when deleting to restore: " + e);
1063        }
1064        setPreferredApnId((long)-1, subId);
1065        mOpenHelper.initDatabase(db);
1066    }
1067
1068    /**
1069     * Log with debug
1070     *
1071     * @param s is string log
1072     */
1073    private static void log(String s) {
1074        Log.d(TAG, s);
1075    }
1076
1077    private static void loge(String s) {
1078        Log.e(TAG, s);
1079    }
1080}
1081