• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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( int efId, int recordNumber, ContentValues values)169     private AdnRecord generateAdnRecordWithNewTagByContentValues(
170             int efId, int recordNumber, ContentValues values) {
171         if (values == null) {
172             return null;
173         }
174         final String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
175         final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
176         final String newEmail = values.getAsString(IccProvider.STR_NEW_EMAILS);
177         final String newAnr = values.getAsString(IccProvider.STR_NEW_ANRS);
178         String[] newEmailArray = TextUtils.isEmpty(newEmail)
179                 ? null : getEmailStringArray(newEmail);
180         String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr);
181         return new AdnRecord(
182                 efId, recordNumber, newTag, newPhoneNumber, newEmailArray, newAnrArray);
183     }
184 
185     /**
186      * Replace oldAdn with newAdn in ADN-like record in EF
187      *
188      * getAdnRecordsInEf must be called at least once before this function,
189      * otherwise an error will be returned.
190      * throws SecurityException if no WRITE_CONTACTS permission
191      *
192      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
193      * @param values old adn tag,  phone number, email and anr to be replaced
194      *        new adn tag,  phone number, email and anr to be stored
195      * @param pin2 required to update EF_FDN, otherwise must be null
196      * @return true for success
197      */
updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values, String pin2)198     public boolean updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values,
199             String pin2) {
200 
201         if (mPhone.getContext().checkCallingOrSelfPermission(
202                 android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
203             throw new SecurityException("Requires android.permission.WRITE_CONTACTS permission");
204         }
205 
206         efid = updateEfForIccType(efid);
207 
208         if (DBG) {
209             logd("updateAdnRecordsWithContentValuesInEfBySearch: efid=" + efid + ", values = " +
210                 values + ", pin2=" + pin2);
211         }
212 
213         checkThread();
214         Request updateRequest = new Request();
215         synchronized (updateRequest) {
216             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
217             AdnRecord oldAdn = generateAdnRecordWithOldTagByContentValues(values);
218             if (usesPbCache(efid)) {
219                 AdnRecord newAdn =
220                         generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN, 0, values);
221                 mSimPbRecordCache.updateSimPbAdnBySearch(oldAdn, newAdn, response);
222                 waitForResult(updateRequest);
223                 return (boolean) updateRequest.mResult;
224             } else {
225                 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, 0, values);
226                 if (mAdnCache != null) {
227                     mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
228                     waitForResult(updateRequest);
229                     return (boolean) updateRequest.mResult;
230                 } else {
231                     loge("Failure while trying to update by search due to uninitialised adncache");
232                     return false;
233                 }
234             }
235         }
236     }
237 
238     /**
239      * Update an ADN-like EF record by record index
240      *
241      * This is useful for iteration the whole ADN file, such as write the whole
242      * phone book or erase/format the whole phonebook. Currently the email field
243      * if set in the ADN record is ignored.
244      * throws SecurityException if no WRITE_CONTACTS permission
245      *
246      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
247      * @param newTag adn tag to be stored
248      * @param newPhoneNumber adn number to be stored
249      *        Set both newTag and newPhoneNumber to "" means to replace the old
250      *        record with empty one, aka, delete old record
251      * @param index is 1-based adn record index to be updated
252      * @param pin2 required to update EF_FDN, otherwise must be null
253      * @return true for success
254      */
255     public boolean
updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2)256     updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2) {
257 
258         if (mPhone.getContext().checkCallingOrSelfPermission(
259                 android.Manifest.permission.WRITE_CONTACTS)
260                 != PackageManager.PERMISSION_GRANTED) {
261             throw new SecurityException(
262                     "Requires android.permission.WRITE_CONTACTS permission");
263         }
264         if (DBG) {
265             logd("updateAdnRecordsInEfByIndex: efid=" + efid + ", values = " +
266                 values + " index=" + index + ", pin2=" + pin2);
267         }
268 
269         checkThread();
270         Request updateRequest = new Request();
271         synchronized (updateRequest) {
272             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
273             if (usesPbCache(efid)) {
274                 AdnRecord newAdn =
275                         generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN,
276                         index, values);
277                 mSimPbRecordCache.updateSimPbAdnByRecordId(index, newAdn, response);
278                 waitForResult(updateRequest);
279                 return (boolean) updateRequest.mResult;
280             } else {
281                 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, index, values);
282                 if (mAdnCache != null) {
283                     mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
284                     waitForResult(updateRequest);
285                     return (boolean) updateRequest.mResult;
286                 } else {
287                     loge("Failure while trying to update by index due to uninitialised adncache");
288                     return false;
289                 }
290             }
291         }
292     }
293 
294     /**
295      * Get the capacity of records in efid
296      *
297      * @param efid the EF id of a ADN-like ICC
298      * @return  int[3] array
299      *            recordSizes[0]  is the single record length
300      *            recordSizes[1]  is the total length of the EF file
301      *            recordSizes[2]  is the number of records in the EF file
302      */
getAdnRecordsSize(int efid)303     public int[] getAdnRecordsSize(int efid) {
304         if (DBG) logd("getAdnRecordsSize: efid=" + efid);
305         checkThread();
306         Request getSizeRequest = new Request();
307         synchronized (getSizeRequest) {
308             //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling
309             Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, getSizeRequest);
310             IccFileHandler fh = mPhone.getIccFileHandler();
311             if (fh != null) {
312                 fh.getEFLinearRecordSize(efid, response);
313                 waitForResult(getSizeRequest);
314             }
315         }
316 
317         return getSizeRequest.mResult == null ? new int[3] : (int[]) getSizeRequest.mResult;
318     }
319 
320 
321     /**
322      * Loads the AdnRecords in efid and returns them as a
323      * List of AdnRecords
324      *
325      * throws SecurityException if no READ_CONTACTS permission
326      *
327      * @param efid the EF id of a ADN-like ICC
328      * @return List of AdnRecord
329      */
getAdnRecordsInEf(int efid)330     public List<AdnRecord> getAdnRecordsInEf(int efid) {
331 
332         if (mPhone.getContext().checkCallingOrSelfPermission(
333                 android.Manifest.permission.READ_CONTACTS)
334                 != PackageManager.PERMISSION_GRANTED) {
335             throw new SecurityException(
336                     "Requires android.permission.READ_CONTACTS permission");
337         }
338 
339         efid = updateEfForIccType(efid);
340         if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase());
341 
342         checkThread();
343         Request loadRequest = new Request();
344         synchronized (loadRequest) {
345             Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest);
346             if (usesPbCache(efid)) {
347                 mSimPbRecordCache.requestLoadAllPbRecords(response);
348                 waitForResult(loadRequest);
349                 return (List<AdnRecord>) loadRequest.mResult;
350             } else {
351                 if (mAdnCache != null) {
352                     mAdnCache.requestLoadAllAdnLike(efid,
353                             mAdnCache.extensionEfForEf(efid), response);
354                     waitForResult(loadRequest);
355                     return (List<AdnRecord>) loadRequest.mResult;
356                 } else {
357                     loge("Failure while trying to load from SIM due to uninitialised adncache");
358                     return null;
359                 }
360             }
361         }
362     }
363 
364     @UnsupportedAppUsage
checkThread()365     protected void checkThread() {
366         // Make sure this isn't the UI thread, since it will block
367         if (mBaseHandler.getLooper().equals(Looper.myLooper())) {
368             loge("query() called on the main UI thread!");
369             throw new IllegalStateException(
370                     "You cannot call query on this provder from the main UI thread.");
371         }
372     }
373 
waitForResult(Request request)374     protected void waitForResult(Request request) {
375         synchronized (request) {
376             while (!request.mStatus.get()) {
377                 try {
378                     request.wait();
379                 } catch (InterruptedException e) {
380                     logd("interrupted while trying to update by search");
381                 }
382             }
383         }
384     }
385 
386     @UnsupportedAppUsage
updateEfForIccType(int efid)387     private int updateEfForIccType(int efid) {
388         // Check if we are trying to read ADN records
389         if (efid == IccConstants.EF_ADN) {
390             if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) {
391                 return IccConstants.EF_PBR;
392             }
393         }
394         return efid;
395     }
396 
getEmailStringArray(String str)397     private String[] getEmailStringArray(String str) {
398         return str != null ? str.split(",") : null;
399     }
400 
getAnrStringArray(String str)401     private String[] getAnrStringArray(String str) {
402         return str != null ? str.split(":") : null;
403     }
404 
405     /**
406      * Get the capacity of ADN records
407      *
408      * @return AdnCapacity
409      */
getAdnRecordsCapacity()410     public AdnCapacity getAdnRecordsCapacity() {
411         if (DBG) logd("getAdnRecordsCapacity" );
412         if (mPhone.getContext().checkCallingOrSelfPermission(
413                 android.Manifest.permission.READ_CONTACTS)
414                 != PackageManager.PERMISSION_GRANTED) {
415             throw new SecurityException(
416                     "Requires android.permission.READ_CONTACTS permission");
417         }
418         int phoneId = mPhone.getPhoneId();
419 
420         UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(phoneId);
421 
422         if (profile != null) {
423             IccCardConstants.State cardstate = profile.getState();
424             if (cardstate == IccCardConstants.State.READY
425                     || cardstate == IccCardConstants.State.LOADED) {
426                 checkThread();
427                 AdnCapacity capacity = mSimPbRecordCache.isEnabled()
428                         ? mSimPbRecordCache.getAdnCapacity() : null;
429                 if (capacity == null) {
430                     loge("Adn capacity is null");
431                     return null;
432                 }
433 
434                 if (DBG) logd("getAdnRecordsCapacity on slot " + phoneId
435                         + ": max adn=" + capacity.getMaxAdnCount()
436                         + ", used adn=" + capacity.getUsedAdnCount()
437                         + ", max email=" + capacity.getMaxEmailCount()
438                         + ", used email=" + capacity.getUsedEmailCount()
439                         + ", max anr=" + capacity.getMaxAnrCount()
440                         + ", used anr=" + capacity.getUsedAnrCount()
441                         + ", max name length="+ capacity.getMaxNameLength()
442                         + ", max number length =" + capacity.getMaxNumberLength()
443                         + ", max email length =" + capacity.getMaxEmailLength()
444                         + ", max anr length =" + capacity.getMaxAnrLength());
445                 return capacity;
446             } else {
447                 logd("No UICC when getAdnRecordsCapacity.");
448             }
449         } else {
450             logd("sim state is not ready when getAdnRecordsCapacity.");
451         }
452         return null;
453     }
454 
usesPbCache(int efid)455     private boolean usesPbCache(int efid) {
456         return mSimPbRecordCache.isEnabled() &&
457                     (efid == IccConstants.EF_PBR || efid == IccConstants.EF_ADN);
458     }
459 }
460