1 /*
2  * Copyright (C) 2006 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.internal.telephony;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.ContentValues;
21 import android.content.pm.PackageManager;
22 import android.os.AsyncResult;
23 import android.os.Build;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.text.TextUtils;
28 
29 import com.android.internal.telephony.uicc.AdnCapacity;
30 import com.android.internal.telephony.uicc.AdnRecord;
31 import com.android.internal.telephony.uicc.AdnRecordCache;
32 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
33 import com.android.internal.telephony.uicc.IccConstants;
34 import com.android.internal.telephony.uicc.IccFileHandler;
35 import com.android.internal.telephony.uicc.IccRecords;
36 import com.android.internal.telephony.uicc.SimPhonebookRecordCache;
37 import com.android.internal.telephony.uicc.UiccController;
38 import com.android.internal.telephony.uicc.UiccProfile;
39 import com.android.telephony.Rlog;
40 
41 import java.util.List;
42 import java.util.Locale;
43 import java.util.concurrent.atomic.AtomicBoolean;
44 
45 /**
46  * IccPhoneBookInterfaceManager to provide an inter-process communication to
47  * access ADN-like SIM records.
48  */
49 public class IccPhoneBookInterfaceManager {
50     static final String LOG_TAG = "IccPhoneBookIM";
51     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
52     protected static final boolean DBG = true;
53 
54     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
55     protected Phone mPhone;
56     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
57     protected AdnRecordCache mAdnCache;
58     protected SimPhonebookRecordCache mSimPbRecordCache;
59 
60     protected static final int EVENT_GET_SIZE_DONE = 1;
61     protected static final int EVENT_LOAD_DONE = 2;
62     protected static final int EVENT_UPDATE_DONE = 3;
63 
64     private static final class Request {
65         AtomicBoolean mStatus = new AtomicBoolean(false);
66         Object mResult = null;
67     }
68 
69     @UnsupportedAppUsage
70     protected Handler mBaseHandler = new Handler() {
71         @Override
72         public void handleMessage(Message msg) {
73             AsyncResult ar = (AsyncResult) msg.obj;
74             Request request = (Request) ar.userObj;
75 
76             switch (msg.what) {
77                 case EVENT_GET_SIZE_DONE:
78                     int[] recordSize = null;
79                     if (ar.exception == null) {
80                         recordSize = (int[]) ar.result;
81                         // recordSize[0]  is the record length
82                         // recordSize[1]  is the total length of the EF file
83                         // recordSize[2]  is the number of records in the EF file
84                         logd("GET_RECORD_SIZE Size " + recordSize[0]
85                                 + " total " + recordSize[1]
86                                 + " #record " + recordSize[2]);
87                     } else {
88                         loge("EVENT_GET_SIZE_DONE: failed; ex=" + ar.exception);
89                     }
90                     notifyPending(request, recordSize);
91                     break;
92                 case EVENT_UPDATE_DONE:
93                     boolean success = (ar.exception == null);
94                     if (!success) {
95                         loge("EVENT_UPDATE_DONE - failed; ex=" + ar.exception);
96                     }
97                     notifyPending(request, success);
98                     break;
99                 case EVENT_LOAD_DONE:
100                     List<AdnRecord> records = null;
101                     if (ar.exception == null) {
102                         records = (List<AdnRecord>) ar.result;
103                     } else {
104                         loge("EVENT_LOAD_DONE: Cannot load ADN records; ex="
105                                 + ar.exception);
106                     }
107                     notifyPending(request, records);
108                     break;
109             }
110         }
111 
112         private void notifyPending(Request request, Object result) {
113             if (request != null) {
114                 synchronized (request) {
115                     request.mResult = result;
116                     request.mStatus.set(true);
117                     request.notifyAll();
118                 }
119             }
120         }
121     };
122 
IccPhoneBookInterfaceManager(Phone phone)123     public IccPhoneBookInterfaceManager(Phone phone) {
124         this.mPhone = phone;
125         IccRecords r = phone.getIccRecords();
126         if (r != null) {
127             mAdnCache = r.getAdnCache();
128         }
129 
130         mSimPbRecordCache = new SimPhonebookRecordCache(
131                 phone.getContext(), phone.getPhoneId(), phone.mCi);
132     }
133 
dispose()134     public void dispose() {
135         mSimPbRecordCache.dispose();
136     }
137 
updateIccRecords(IccRecords iccRecords)138     public void updateIccRecords(IccRecords iccRecords) {
139         if (iccRecords != null) {
140             mAdnCache = iccRecords.getAdnCache();
141         } else {
142             mAdnCache = null;
143         }
144     }
145 
146     @UnsupportedAppUsage
logd(String msg)147     protected void logd(String msg) {
148         Rlog.d(LOG_TAG, "[IccPbInterfaceManager] " + msg);
149     }
150 
151     @UnsupportedAppUsage
loge(String msg)152     protected void loge(String msg) {
153         Rlog.e(LOG_TAG, "[IccPbInterfaceManager] " + msg);
154     }
155 
generateAdnRecordWithOldTagByContentValues(ContentValues values)156     private AdnRecord generateAdnRecordWithOldTagByContentValues(ContentValues values) {
157         if (values == null) {
158             return null;
159         }
160         final String oldTag = values.getAsString(IccProvider.STR_TAG);
161         final String oldPhoneNumber = values.getAsString(IccProvider.STR_NUMBER);
162         final String oldEmail = values.getAsString(IccProvider.STR_EMAILS);
163         final String oldAnr = values.getAsString(IccProvider.STR_ANRS);;
164         String[] oldEmailArray = TextUtils.isEmpty(oldEmail)
165                 ? null : getEmailStringArray(oldEmail);
166         String[] oldAnrArray = TextUtils.isEmpty(oldAnr) ? null : getAnrStringArray(oldAnr);
167         return new AdnRecord(oldTag, oldPhoneNumber, oldEmailArray, oldAnrArray);
168     }
169 
generateAdnRecordWithNewTagByContentValues( int efId, int recordNumber, ContentValues values)170     private AdnRecord generateAdnRecordWithNewTagByContentValues(
171             int efId, int recordNumber, ContentValues values) {
172         if (values == null) {
173             return null;
174         }
175         final String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
176         final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
177         final String newEmail = values.getAsString(IccProvider.STR_NEW_EMAILS);
178         final String newAnr = values.getAsString(IccProvider.STR_NEW_ANRS);
179         String[] newEmailArray = TextUtils.isEmpty(newEmail)
180                 ? null : getEmailStringArray(newEmail);
181         String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr);
182         return new AdnRecord(
183                 efId, recordNumber, newTag, newPhoneNumber, newEmailArray, newAnrArray);
184     }
185 
186     /**
187      * Replace oldAdn with newAdn in ADN-like record in EF
188      *
189      * getAdnRecordsInEf must be called at least once before this function,
190      * otherwise an error will be returned.
191      * throws SecurityException if no WRITE_CONTACTS permission
192      *
193      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
194      * @param values old adn tag,  phone number, email and anr to be replaced
195      *        new adn tag,  phone number, email and anr to be stored
196      * @param pin2 required to update EF_FDN, otherwise must be null
197      * @return true for success
198      */
updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values, String pin2)199     public boolean updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values,
200             String pin2) {
201 
202         if (mPhone.getContext().checkCallingOrSelfPermission(
203                 android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
204             throw new SecurityException("Requires android.permission.WRITE_CONTACTS permission");
205         }
206 
207         efid = updateEfForIccType(efid);
208 
209         if (DBG) {
210             logd("updateAdnRecordsWithContentValuesInEfBySearch: efid=" + efid + ", values = " +
211                 values + ", pin2=" + pin2);
212         }
213 
214         checkThread();
215         Request updateRequest = new Request();
216         synchronized (updateRequest) {
217             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
218             AdnRecord oldAdn = generateAdnRecordWithOldTagByContentValues(values);
219             if (usesPbCache(efid)) {
220                 AdnRecord newAdn =
221                         generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN, 0, values);
222                 mSimPbRecordCache.updateSimPbAdnBySearch(oldAdn, newAdn, response);
223                 waitForResult(updateRequest);
224                 return (boolean) updateRequest.mResult;
225             } else {
226                 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, 0, values);
227                 if (mAdnCache != null) {
228                     mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
229                     waitForResult(updateRequest);
230                     return (boolean) updateRequest.mResult;
231                 } else {
232                     loge("Failure while trying to update by search due to uninitialised adncache");
233                     return false;
234                 }
235             }
236         }
237     }
238 
239     /**
240      * Update an ADN-like EF record by record index
241      *
242      * This is useful for iteration the whole ADN file, such as write the whole
243      * phone book or erase/format the whole phonebook. Currently the email field
244      * if set in the ADN record is ignored.
245      * throws SecurityException if no WRITE_CONTACTS permission
246      *
247      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
248      * @param newTag adn tag to be stored
249      * @param newPhoneNumber adn number to be stored
250      *        Set both newTag and newPhoneNumber to "" means to replace the old
251      *        record with empty one, aka, delete old record
252      * @param index is 1-based adn record index to be updated
253      * @param pin2 required to update EF_FDN, otherwise must be null
254      * @return true for success
255      */
256     public boolean
updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2)257     updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2) {
258 
259         if (mPhone.getContext().checkCallingOrSelfPermission(
260                 android.Manifest.permission.WRITE_CONTACTS)
261                 != PackageManager.PERMISSION_GRANTED) {
262             throw new SecurityException(
263                     "Requires android.permission.WRITE_CONTACTS permission");
264         }
265         if (DBG) {
266             logd("updateAdnRecordsInEfByIndex: efid=" + efid + ", values = " +
267                 values + " index=" + index + ", pin2=" + pin2);
268         }
269 
270         checkThread();
271         Request updateRequest = new Request();
272         synchronized (updateRequest) {
273             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
274             if (usesPbCache(efid)) {
275                 AdnRecord newAdn =
276                         generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN,
277                         index, values);
278                 mSimPbRecordCache.updateSimPbAdnByRecordId(index, newAdn, response);
279                 waitForResult(updateRequest);
280                 return (boolean) updateRequest.mResult;
281             } else {
282                 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, index, values);
283                 if (mAdnCache != null) {
284                     mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
285                     waitForResult(updateRequest);
286                     return (boolean) updateRequest.mResult;
287                 } else {
288                     loge("Failure while trying to update by index due to uninitialised adncache");
289                     return false;
290                 }
291             }
292         }
293     }
294 
295     /**
296      * Get the capacity of records in efid
297      *
298      * @param efid the EF id of a ADN-like ICC
299      * @return  int[3] array
300      *            recordSizes[0]  is the single record length
301      *            recordSizes[1]  is the total length of the EF file
302      *            recordSizes[2]  is the number of records in the EF file
303      */
getAdnRecordsSize(int efid)304     public int[] getAdnRecordsSize(int efid) {
305         if (DBG) logd("getAdnRecordsSize: efid=" + efid);
306         checkThread();
307         Request getSizeRequest = new Request();
308         synchronized (getSizeRequest) {
309             //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling
310             Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, getSizeRequest);
311             IccFileHandler fh = mPhone.getIccFileHandler();
312             if (fh != null) {
313                 fh.getEFLinearRecordSize(efid, response);
314                 waitForResult(getSizeRequest);
315             }
316         }
317 
318         return getSizeRequest.mResult == null ? new int[3] : (int[]) getSizeRequest.mResult;
319     }
320 
321 
322     /**
323      * Loads the AdnRecords in efid and returns them as a
324      * List of AdnRecords
325      *
326      * throws SecurityException if no READ_CONTACTS permission
327      *
328      * @param efid the EF id of a ADN-like ICC
329      * @return List of AdnRecord
330      */
getAdnRecordsInEf(int efid)331     public List<AdnRecord> getAdnRecordsInEf(int efid) {
332 
333         if (mPhone.getContext().checkCallingOrSelfPermission(
334                 android.Manifest.permission.READ_CONTACTS)
335                 != PackageManager.PERMISSION_GRANTED) {
336             throw new SecurityException(
337                     "Requires android.permission.READ_CONTACTS permission");
338         }
339 
340         efid = updateEfForIccType(efid);
341         if (DBG) {
342             logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid)
343                     .toUpperCase(Locale.ROOT));
344         }
345 
346         checkThread();
347         Request loadRequest = new Request();
348         synchronized (loadRequest) {
349             Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest);
350             if (usesPbCache(efid)) {
351                 mSimPbRecordCache.requestLoadAllPbRecords(response);
352                 waitForResult(loadRequest);
353                 return (List<AdnRecord>) loadRequest.mResult;
354             } else {
355                 if (mAdnCache != null) {
356                     mAdnCache.requestLoadAllAdnLike(efid,
357                             mAdnCache.extensionEfForEf(efid), response);
358                     waitForResult(loadRequest);
359                     return (List<AdnRecord>) loadRequest.mResult;
360                 } else {
361                     loge("Failure while trying to load from SIM due to uninitialised adncache");
362                     return null;
363                 }
364             }
365         }
366     }
367 
368     @UnsupportedAppUsage
checkThread()369     protected void checkThread() {
370         // Make sure this isn't the UI thread, since it will block
371         if (mBaseHandler.getLooper().equals(Looper.myLooper())) {
372             loge("query() called on the main UI thread!");
373             throw new IllegalStateException(
374                     "You cannot call query on this provder from the main UI thread.");
375         }
376     }
377 
waitForResult(Request request)378     protected void waitForResult(Request request) {
379         synchronized (request) {
380             while (!request.mStatus.get()) {
381                 try {
382                     request.wait();
383                 } catch (InterruptedException e) {
384                     logd("interrupted while trying to update by search");
385                 }
386             }
387         }
388     }
389 
390     @UnsupportedAppUsage
updateEfForIccType(int efid)391     private int updateEfForIccType(int efid) {
392         // Check if we are trying to read ADN records
393         if (efid == IccConstants.EF_ADN) {
394             if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) {
395                 return IccConstants.EF_PBR;
396             }
397         }
398         return efid;
399     }
400 
getEmailStringArray(String str)401     private String[] getEmailStringArray(String str) {
402         return str != null ? str.split(",") : null;
403     }
404 
getAnrStringArray(String str)405     private String[] getAnrStringArray(String str) {
406         return str != null ? str.split(":") : null;
407     }
408 
409     /**
410      * Get the capacity of ADN records
411      *
412      * @return AdnCapacity
413      */
getAdnRecordsCapacity()414     public AdnCapacity getAdnRecordsCapacity() {
415         if (DBG) logd("getAdnRecordsCapacity" );
416         if (mPhone.getContext().checkCallingOrSelfPermission(
417                 android.Manifest.permission.READ_CONTACTS)
418                 != PackageManager.PERMISSION_GRANTED) {
419             throw new SecurityException(
420                     "Requires android.permission.READ_CONTACTS permission");
421         }
422         int phoneId = mPhone.getPhoneId();
423 
424         UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(phoneId);
425 
426         if (profile != null) {
427             IccCardConstants.State cardstate = profile.getState();
428             if (cardstate == IccCardConstants.State.READY
429                     || cardstate == IccCardConstants.State.LOADED) {
430                 checkThread();
431                 AdnCapacity capacity = mSimPbRecordCache.isEnabled()
432                         ? mSimPbRecordCache.getAdnCapacity() : null;
433                 if (capacity == null) {
434                     loge("Adn capacity is null");
435                     return null;
436                 }
437 
438                 if (DBG) logd("getAdnRecordsCapacity on slot " + phoneId
439                         + ": max adn=" + capacity.getMaxAdnCount()
440                         + ", used adn=" + capacity.getUsedAdnCount()
441                         + ", max email=" + capacity.getMaxEmailCount()
442                         + ", used email=" + capacity.getUsedEmailCount()
443                         + ", max anr=" + capacity.getMaxAnrCount()
444                         + ", used anr=" + capacity.getUsedAnrCount()
445                         + ", max name length="+ capacity.getMaxNameLength()
446                         + ", max number length =" + capacity.getMaxNumberLength()
447                         + ", max email length =" + capacity.getMaxEmailLength()
448                         + ", max anr length =" + capacity.getMaxAnrLength());
449                 return capacity;
450             } else {
451                 logd("No UICC when getAdnRecordsCapacity.");
452             }
453         } else {
454             logd("sim state is not ready when getAdnRecordsCapacity.");
455         }
456         return null;
457     }
458 
usesPbCache(int efid)459     private boolean usesPbCache(int efid) {
460         return mSimPbRecordCache.isEnabled() &&
461                     (efid == IccConstants.EF_PBR || efid == IccConstants.EF_ADN);
462     }
463 }
464