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