1/*
2 * Copyright (C) 2017 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.providers.telephony;
18
19import android.content.ContentProvider;
20import android.content.ContentUris;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.content.UriMatcher;
25import android.content.pm.PackageManager;
26import android.database.Cursor;
27import android.database.MatrixCursor;
28import android.database.sqlite.SQLiteDatabase;
29import android.database.sqlite.SQLiteOpenHelper;
30import android.database.sqlite.SQLiteQueryBuilder;
31import android.net.Uri;
32import android.os.Environment;
33import android.provider.Telephony.CarrierId;
34import android.telephony.SubscriptionManager;
35import android.text.TextUtils;
36import android.util.Log;
37import android.util.Pair;
38
39import com.android.internal.annotations.VisibleForTesting;
40import com.android.internal.telephony.SubscriptionController;
41import com.android.internal.telephony.nano.CarrierIdProto;
42
43import java.io.ByteArrayOutputStream;
44import java.io.File;
45import java.io.FileInputStream;
46import java.io.IOException;
47import java.io.InputStream;
48import java.util.ArrayList;
49import java.util.Arrays;
50import java.util.List;
51import java.util.Map;
52import java.util.concurrent.ConcurrentHashMap;
53
54import libcore.io.IoUtils;
55
56/**
57 * This class provides the ability to query the Carrier Identification databases
58 * (A.K.A. cid) which is stored in a SQLite database.
59 *
60 * Each row in carrier identification db consists of matching rule (e.g., MCCMNC, GID1, GID2, PLMN)
61 * and its matched carrier id & carrier name. Each carrier either MNO or MVNO could be
62 * identified by multiple matching rules but is assigned with a unique ID (cid).
63 *
64 *
65 * This class provides the ability to retrieve the cid of the current subscription.
66 * This is done atomically through a query.
67 *
68 * This class also provides a way to update carrier identifying attributes of an existing entry.
69 * Insert entries for new carriers or an existing carrier.
70 */
71public class CarrierIdProvider extends ContentProvider {
72
73    private static final boolean VDBG = false; // STOPSHIP if true
74    private static final String TAG = CarrierIdProvider.class.getSimpleName();
75
76    private static final String DATABASE_NAME = "carrierIdentification.db";
77    private static final int DATABASE_VERSION = 3;
78
79    private static final String ASSETS_PB_FILE = "carrier_list.pb";
80    private static final String VERSION_KEY = "version";
81    private static final String OTA_UPDATED_PB_PATH = "misc/carrierid/" + ASSETS_PB_FILE;
82    private static final String PREF_FILE = CarrierIdProvider.class.getSimpleName();
83
84    private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
85
86    private static final int URL_ALL                = 1;
87    private static final int URL_ALL_UPDATE_FROM_PB = 2;
88    private static final int URL_ALL_GET_VERSION    = 3;
89
90    /**
91     * index 0: {@link CarrierId.All#MCCMNC}
92     */
93    private static final int MCCMNC_INDEX                = 0;
94    /**
95     * index 1: {@link CarrierId.All#IMSI_PREFIX_XPATTERN}
96     */
97    private static final int IMSI_PREFIX_INDEX           = 1;
98    /**
99     * index 2: {@link CarrierId.All#GID1}
100     */
101    private static final int GID1_INDEX                  = 2;
102    /**
103     * index 3: {@link CarrierId.All#GID2}
104     */
105    private static final int GID2_INDEX                  = 3;
106    /**
107     * index 4: {@link CarrierId.All#PLMN}
108     */
109    private static final int PLMN_INDEX                  = 4;
110    /**
111     * index 5: {@link CarrierId.All#SPN}
112     */
113    private static final int SPN_INDEX                   = 5;
114    /**
115     * index 6: {@link CarrierId.All#APN}
116     */
117    private static final int APN_INDEX                   = 6;
118    /**
119    * index 7: {@link CarrierId.All#ICCID_PREFIX}
120    */
121    private static final int ICCID_PREFIX_INDEX          = 7;
122    /**
123     * ending index of carrier attribute list.
124     */
125    private static final int CARRIER_ATTR_END_IDX        = ICCID_PREFIX_INDEX;
126    /**
127     * The authority string for the CarrierIdProvider
128     */
129    @VisibleForTesting
130    public static final String AUTHORITY = "carrier_id";
131
132    public static final String CARRIER_ID_TABLE = "carrier_id";
133
134    private static final List<String> CARRIERS_ID_UNIQUE_FIELDS = new ArrayList<>(Arrays.asList(
135            CarrierId.All.MCCMNC,
136            CarrierId.All.GID1,
137            CarrierId.All.GID2,
138            CarrierId.All.PLMN,
139            CarrierId.All.IMSI_PREFIX_XPATTERN,
140            CarrierId.All.SPN,
141            CarrierId.All.APN,
142            CarrierId.All.ICCID_PREFIX));
143
144    private CarrierIdDatabaseHelper mDbHelper;
145
146    /**
147     * Stores carrier id information for the current active subscriptions.
148     * Key is the active subId and entryValue is a pair of carrier id(int) and Carrier Name(String).
149     */
150    private final Map<Integer, Pair<Integer, String>> mCurrentSubscriptionMap =
151            new ConcurrentHashMap<>();
152
153    @VisibleForTesting
154    public static String getStringForCarrierIdTableCreation(String tableName) {
155        return "CREATE TABLE " + tableName
156                + "(_id INTEGER PRIMARY KEY,"
157                + CarrierId.All.MCCMNC + " TEXT NOT NULL,"
158                + CarrierId.All.GID1 + " TEXT,"
159                + CarrierId.All.GID2 + " TEXT,"
160                + CarrierId.All.PLMN + " TEXT,"
161                + CarrierId.All.IMSI_PREFIX_XPATTERN + " TEXT,"
162                + CarrierId.All.SPN + " TEXT,"
163                + CarrierId.All.APN + " TEXT,"
164                + CarrierId.All.ICCID_PREFIX + " TEXT,"
165                + CarrierId.CARRIER_NAME + " TEXT,"
166                + CarrierId.CARRIER_ID + " INTEGER DEFAULT -1,"
167                + "UNIQUE (" + TextUtils.join(", ", CARRIERS_ID_UNIQUE_FIELDS) + "));";
168    }
169
170    @VisibleForTesting
171    public static String getStringForIndexCreation(String tableName) {
172        return "CREATE INDEX IF NOT EXISTS mccmncIndex ON " + tableName + " ("
173                + CarrierId.All.MCCMNC + ");";
174    }
175
176    @Override
177    public boolean onCreate() {
178        Log.d(TAG, "onCreate");
179        mDbHelper = new CarrierIdDatabaseHelper(getContext());
180        mDbHelper.getReadableDatabase();
181        s_urlMatcher.addURI(AUTHORITY, "all", URL_ALL);
182        s_urlMatcher.addURI(AUTHORITY, "all/update_db", URL_ALL_UPDATE_FROM_PB);
183        s_urlMatcher.addURI(AUTHORITY, "all/get_version", URL_ALL_GET_VERSION);
184        updateDatabaseFromPb(mDbHelper.getWritableDatabase());
185        return true;
186    }
187
188    @Override
189    public String getType(Uri uri) {
190        Log.d(TAG, "getType");
191        return null;
192    }
193
194    @Override
195    public Cursor query(Uri uri, String[] projectionIn, String selection,
196                        String[] selectionArgs, String sortOrder) {
197        if (VDBG) {
198            Log.d(TAG, "query:"
199                    + " uri=" + uri
200                    + " values=" + Arrays.toString(projectionIn)
201                    + " selection=" + selection
202                    + " selectionArgs=" + Arrays.toString(selectionArgs));
203        }
204
205        final int match = s_urlMatcher.match(uri);
206        switch (match) {
207            case URL_ALL_GET_VERSION:
208                checkReadPermission();
209                final MatrixCursor cursor = new MatrixCursor(new String[] {VERSION_KEY});
210                cursor.addRow(new Object[] {getAppliedVersion()});
211                return cursor;
212            case URL_ALL:
213                checkReadPermission();
214                SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
215                qb.setTables(CARRIER_ID_TABLE);
216
217                SQLiteDatabase db = getReadableDatabase();
218                return qb.query(db, projectionIn, selection, selectionArgs, null, null, sortOrder);
219            default:
220                return queryCarrierIdForCurrentSubscription(uri, projectionIn);
221        }
222    }
223
224    @Override
225    public Uri insert(Uri uri, ContentValues values) {
226        checkWritePermission();
227        final int match = s_urlMatcher.match(uri);
228        switch (match) {
229            case URL_ALL:
230                final long row = getWritableDatabase().insertOrThrow(CARRIER_ID_TABLE, null,
231                        values);
232                if (row > 0) {
233                    final Uri newUri = ContentUris.withAppendedId(
234                            CarrierId.All.CONTENT_URI, row);
235                    getContext().getContentResolver().notifyChange(
236                            CarrierId.All.CONTENT_URI, null);
237                    return newUri;
238                }
239                return null;
240            default:
241                throw new IllegalArgumentException("Cannot insert that URL: " + uri);
242        }
243    }
244
245    @Override
246    public int delete(Uri uri, String selection, String[] selectionArgs) {
247        checkWritePermission();
248        if (VDBG) {
249            Log.d(TAG, "delete:"
250                    + " uri=" + uri
251                    + " selection={" + selection + "}"
252                    + " selection=" + selection
253                    + " selectionArgs=" + Arrays.toString(selectionArgs));
254        }
255        final int match = s_urlMatcher.match(uri);
256        switch (match) {
257            case URL_ALL:
258                final int count = getWritableDatabase().delete(CARRIER_ID_TABLE, selection,
259                        selectionArgs);
260                Log.d(TAG, "  delete.count=" + count);
261                if (count > 0) {
262                    getContext().getContentResolver().notifyChange(
263                            CarrierId.All.CONTENT_URI, null);
264                }
265                return count;
266            default:
267                throw new IllegalArgumentException("Cannot delete that URL: " + uri);
268        }
269    }
270
271    @Override
272    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
273        checkWritePermission();
274        if (VDBG) {
275            Log.d(TAG, "update:"
276                    + " uri=" + uri
277                    + " values={" + values + "}"
278                    + " selection=" + selection
279                    + " selectionArgs=" + Arrays.toString(selectionArgs));
280        }
281
282        final int match = s_urlMatcher.match(uri);
283        switch (match) {
284            case URL_ALL_UPDATE_FROM_PB:
285                return updateDatabaseFromPb(getWritableDatabase());
286            case URL_ALL:
287                final int count = getWritableDatabase().update(CARRIER_ID_TABLE, values, selection,
288                        selectionArgs);
289                Log.d(TAG, "  update.count=" + count);
290                if (count > 0) {
291                    getContext().getContentResolver().notifyChange(CarrierId.All.CONTENT_URI, null);
292                }
293                return count;
294            default:
295                return updateCarrierIdForCurrentSubscription(uri, values);
296
297        }
298    }
299
300    /**
301     * These methods can be overridden in a subclass for testing CarrierIdProvider using an
302     * in-memory database.
303     */
304    SQLiteDatabase getReadableDatabase() {
305        return mDbHelper.getReadableDatabase();
306    }
307    SQLiteDatabase getWritableDatabase() {
308        return mDbHelper.getWritableDatabase();
309    }
310
311    private class CarrierIdDatabaseHelper extends SQLiteOpenHelper {
312        private final String TAG = CarrierIdDatabaseHelper.class.getSimpleName();
313
314        /**
315         * CarrierIdDatabaseHelper carrier identification database helper class.
316         * @param context of the user.
317         */
318        public CarrierIdDatabaseHelper(Context context) {
319            super(context, DATABASE_NAME, null, DATABASE_VERSION);
320        }
321
322        @Override
323        public void onCreate(SQLiteDatabase db) {
324            Log.d(TAG, "onCreate");
325            db.execSQL(getStringForCarrierIdTableCreation(CARRIER_ID_TABLE));
326            db.execSQL(getStringForIndexCreation(CARRIER_ID_TABLE));
327        }
328
329        public void createCarrierTable(SQLiteDatabase db) {
330            db.execSQL(getStringForCarrierIdTableCreation(CARRIER_ID_TABLE));
331            db.execSQL(getStringForIndexCreation(CARRIER_ID_TABLE));
332        }
333
334        public void dropCarrierTable(SQLiteDatabase db) {
335            db.execSQL("DROP TABLE IF EXISTS " + CARRIER_ID_TABLE + ";");
336        }
337
338        @Override
339        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
340            Log.d(TAG, "dbh.onUpgrade:+ db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
341            if (oldVersion < DATABASE_VERSION) {
342                dropCarrierTable(db);
343                createCarrierTable(db);
344            }
345        }
346    }
347
348    /**
349     * Parse and persist pb file as database default values.
350     * Use version number to detect file update.
351     * Update database with data from assets or ota only if version jumps.
352     */
353    private int updateDatabaseFromPb(SQLiteDatabase db) {
354        Log.d(TAG, "update database from pb file");
355        int rows = 0;
356        CarrierIdProto.CarrierList carrierList = getUpdateCarrierList();
357        // No update is needed
358        if (carrierList == null) return rows;
359
360        ContentValues cv;
361        List<ContentValues> cvs;
362        try {
363            // Batch all insertions in a single transaction to improve efficiency.
364            db.beginTransaction();
365            db.delete(CARRIER_ID_TABLE, null, null);
366            for (CarrierIdProto.CarrierId id : carrierList.carrierId) {
367                for (CarrierIdProto.CarrierAttribute attr : id.carrierAttribute) {
368                    cv = new ContentValues();
369                    cv.put(CarrierId.CARRIER_ID, id.canonicalId);
370                    cv.put(CarrierId.CARRIER_NAME, id.carrierName);
371                    cvs = new ArrayList<>();
372                    convertCarrierAttrToContentValues(cv, cvs, attr, 0);
373                    for (ContentValues contentVal : cvs) {
374                        // When a constraint violation occurs, the row that contains the violation
375                        // is not inserted. But the command continues executing normally.
376                        if (db.insertWithOnConflict(CARRIER_ID_TABLE, null, contentVal,
377                                SQLiteDatabase.CONFLICT_IGNORE) > 0) {
378                            rows++;
379                        } else {
380                            Log.e(TAG, "updateDatabaseFromPB insertion failure, row: "
381                                    + rows + "carrier id: " + id.canonicalId);
382                            // TODO metrics
383                        }
384                    }
385                }
386            }
387            Log.d(TAG, "update database from pb. inserted rows = " + rows);
388            if (rows > 0) {
389                // Notify listener of DB change
390                getContext().getContentResolver().notifyChange(CarrierId.All.CONTENT_URI, null);
391            }
392            setAppliedVersion(carrierList.version);
393            db.setTransactionSuccessful();
394        } finally {
395            db.endTransaction();
396        }
397        return rows;
398    }
399
400    /**
401     * Recursively loop through carrier attribute list to get all combinations.
402     */
403    private void convertCarrierAttrToContentValues(ContentValues cv, List<ContentValues> cvs,
404            CarrierIdProto.CarrierAttribute attr, int index) {
405        if (index > CARRIER_ATTR_END_IDX) {
406            cvs.add(new ContentValues(cv));
407            return;
408        }
409        boolean found = false;
410        switch (index) {
411            case MCCMNC_INDEX:
412                for (String str : attr.mccmncTuple) {
413                    cv.put(CarrierId.All.MCCMNC, str);
414                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
415                    cv.remove(CarrierId.All.MCCMNC);
416                    found = true;
417                }
418                break;
419            case IMSI_PREFIX_INDEX:
420                for (String str : attr.imsiPrefixXpattern) {
421                    cv.put(CarrierId.All.IMSI_PREFIX_XPATTERN, str);
422                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
423                    cv.remove(CarrierId.All.IMSI_PREFIX_XPATTERN);
424                    found = true;
425                }
426                break;
427            case GID1_INDEX:
428                for (String str : attr.gid1) {
429                    cv.put(CarrierId.All.GID1, str);
430                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
431                    cv.remove(CarrierId.All.GID1);
432                    found = true;
433                }
434                break;
435            case GID2_INDEX:
436                for (String str : attr.gid2) {
437                    cv.put(CarrierId.All.GID2, str);
438                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
439                    cv.remove(CarrierId.All.GID2);
440                    found = true;
441                }
442                break;
443            case PLMN_INDEX:
444                for (String str : attr.plmn) {
445                    cv.put(CarrierId.All.PLMN, str);
446                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
447                    cv.remove(CarrierId.All.PLMN);
448                    found = true;
449                }
450                break;
451            case SPN_INDEX:
452                for (String str : attr.spn) {
453                    cv.put(CarrierId.All.SPN, str);
454                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
455                    cv.remove(CarrierId.All.SPN);
456                    found = true;
457                }
458                break;
459            case APN_INDEX:
460                for (String str : attr.preferredApn) {
461                    cv.put(CarrierId.All.APN, str);
462                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
463                    cv.remove(CarrierId.All.APN);
464                    found = true;
465                }
466                break;
467            case ICCID_PREFIX_INDEX:
468                for (String str : attr.iccidPrefix) {
469                    cv.put(CarrierId.All.ICCID_PREFIX, str);
470                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
471                    cv.remove(CarrierId.All.ICCID_PREFIX);
472                    found = true;
473                }
474                break;
475            default:
476                Log.e(TAG, "unsupported index: " + index);
477                break;
478        }
479        // if attribute at index is empty, move forward to the next attribute
480        if (!found) {
481            convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
482        }
483    }
484
485    /**
486     * Return the update carrierList.
487     * Get the latest version from the last applied, assets and ota file. if the latest version
488     * is newer than the last applied, update is required. Otherwise no update is required and
489     * the returned carrierList will be null.
490     */
491    private CarrierIdProto.CarrierList getUpdateCarrierList() {
492        int version = getAppliedVersion();
493        CarrierIdProto.CarrierList carrierList = null;
494        CarrierIdProto.CarrierList assets = null;
495        CarrierIdProto.CarrierList ota = null;
496        InputStream is = null;
497
498        try {
499            is = getContext().getAssets().open(ASSETS_PB_FILE);
500            assets = CarrierIdProto.CarrierList.parseFrom(readInputStreamToByteArray(is));
501        } catch (IOException ex) {
502            Log.e(TAG, "read carrier list from assets pb failure: " + ex);
503        } finally {
504            IoUtils.closeQuietly(is);
505        }
506        try {
507            is = new FileInputStream(new File(Environment.getDataDirectory(), OTA_UPDATED_PB_PATH));
508            ota = CarrierIdProto.CarrierList.parseFrom(readInputStreamToByteArray(is));
509        } catch (IOException ex) {
510            Log.e(TAG, "read carrier list from ota pb failure: " + ex);
511        } finally {
512            IoUtils.closeQuietly(is);
513        }
514
515        // compare version
516        if (assets != null && assets.version > version) {
517            carrierList = assets;
518            version = assets.version;
519        }
520        if (ota != null && ota.version > version) {
521            carrierList = ota;
522            version = ota.version;
523        }
524        Log.d(TAG, "latest version: " + version + " need update: " + (carrierList != null));
525        return carrierList;
526    }
527
528    private int getAppliedVersion() {
529        final SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE,
530                Context.MODE_PRIVATE);
531        return sp.getInt(VERSION_KEY, -1);
532    }
533
534    private void setAppliedVersion(int version) {
535        final SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE,
536                Context.MODE_PRIVATE);
537        SharedPreferences.Editor editor = sp.edit();
538        editor.putInt(VERSION_KEY, version);
539        editor.apply();
540    }
541
542    /**
543     * Util function to convert inputStream to byte array before parsing proto data.
544     */
545    private static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException {
546        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
547        int nRead;
548        int size = 16 * 1024; // Read 16k chunks
549        byte[] data = new byte[size];
550        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
551            buffer.write(data, 0, nRead);
552        }
553        buffer.flush();
554        return buffer.toByteArray();
555    }
556
557    private int updateCarrierIdForCurrentSubscription(Uri uri, ContentValues cv) {
558        // Parse the subId
559        int subId;
560        try {
561            subId = Integer.parseInt(uri.getLastPathSegment());
562        } catch (NumberFormatException e) {
563            throw new IllegalArgumentException("invalid subid in provided uri " + uri);
564        }
565        Log.d(TAG, "updateCarrierIdForSubId: " + subId);
566
567        // Handle DEFAULT_SUBSCRIPTION_ID
568        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
569            subId = SubscriptionController.getInstance().getDefaultSubId();
570        }
571
572        if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
573            // Remove absent subId from the currentSubscriptionMap.
574            final List activeSubscriptions = Arrays.asList(SubscriptionController.getInstance()
575                    .getActiveSubIdList());
576            int count = 0;
577            for (int subscription : mCurrentSubscriptionMap.keySet()) {
578                if (!activeSubscriptions.contains(subscription)) {
579                    count++;
580                    Log.d(TAG, "updateCarrierIdForSubId: " + subscription);
581                    mCurrentSubscriptionMap.remove(subscription);
582                    getContext().getContentResolver().notifyChange(CarrierId.CONTENT_URI, null);
583                }
584            }
585            return count;
586        } else {
587            mCurrentSubscriptionMap.put(subId,
588                    new Pair(cv.getAsInteger(CarrierId.CARRIER_ID),
589                    cv.getAsString(CarrierId.CARRIER_NAME)));
590            getContext().getContentResolver().notifyChange(CarrierId.CONTENT_URI, null);
591            return 1;
592        }
593    }
594
595    private Cursor queryCarrierIdForCurrentSubscription(Uri uri, String[] projectionIn) {
596        // Parse the subId, using the default subId if subId is not provided
597        int subId = SubscriptionController.getInstance().getDefaultSubId();
598        if (!TextUtils.isEmpty(uri.getLastPathSegment())) {
599            try {
600                subId = Integer.parseInt(uri.getLastPathSegment());
601            } catch (NumberFormatException e) {
602                throw new IllegalArgumentException("invalid subid in provided uri" + uri);
603            }
604        }
605        Log.d(TAG, "queryCarrierIdForSubId: " + subId);
606
607        // Handle DEFAULT_SUBSCRIPTION_ID
608        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
609            subId = SubscriptionController.getInstance().getDefaultSubId();
610        }
611
612        if (!mCurrentSubscriptionMap.containsKey(subId)) {
613            // Return an empty cursor if subId is not belonging to current subscriptions.
614            return new MatrixCursor(projectionIn, 0);
615        }
616        final MatrixCursor c = new MatrixCursor(projectionIn, 1);
617        final MatrixCursor.RowBuilder row = c.newRow();
618        for (int i = 0; i < c.getColumnCount(); i++) {
619            final String columnName = c.getColumnName(i);
620            if (CarrierId.CARRIER_ID.equals(columnName)) {
621                row.add(mCurrentSubscriptionMap.get(subId).first);
622            } else if (CarrierId.CARRIER_NAME.equals(columnName)) {
623                row.add(mCurrentSubscriptionMap.get(subId).second);
624            } else {
625                throw new IllegalArgumentException("Invalid column " + projectionIn[i]);
626            }
627        }
628        return c;
629    }
630
631    private void checkReadPermission() {
632        int status = getContext().checkCallingOrSelfPermission(
633                "android.permission.READ_PRIVILEGED_PHONE_STATE");
634        if (status == PackageManager.PERMISSION_GRANTED) {
635            return;
636        }
637        throw new SecurityException("No permission to read CarrierId provider");
638    }
639
640    private void checkWritePermission() {
641        int status = getContext().checkCallingOrSelfPermission(
642                "android.permission.MODIFY_PHONE_STATE");
643        if (status == PackageManager.PERMISSION_GRANTED) {
644            return;
645        }
646        throw new SecurityException("No permission to write CarrierId provider");
647    }
648}
649