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