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