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