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.pm.PackageManager;
21 import android.os.AsyncResult;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 
26 import com.android.internal.telephony.uicc.AdnRecord;
27 import com.android.internal.telephony.uicc.AdnRecordCache;
28 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
29 import com.android.internal.telephony.uicc.IccConstants;
30 import com.android.internal.telephony.uicc.IccFileHandler;
31 import com.android.internal.telephony.uicc.IccRecords;
32 import com.android.telephony.Rlog;
33 
34 import java.util.List;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 
37 /**
38  * SimPhoneBookInterfaceManager to provide an inter-process communication to
39  * access ADN-like SIM records.
40  */
41 public class IccPhoneBookInterfaceManager {
42     static final String LOG_TAG = "IccPhoneBookIM";
43     @UnsupportedAppUsage
44     protected static final boolean DBG = true;
45 
46     @UnsupportedAppUsage
47     protected Phone mPhone;
48     @UnsupportedAppUsage
49     protected AdnRecordCache mAdnCache;
50 
51     protected static final int EVENT_GET_SIZE_DONE = 1;
52     protected static final int EVENT_LOAD_DONE = 2;
53     protected static final int EVENT_UPDATE_DONE = 3;
54 
55     private static final class Request {
56         AtomicBoolean mStatus = new AtomicBoolean(false);
57         Object mResult = null;
58     }
59 
60     @UnsupportedAppUsage
61     protected Handler mBaseHandler = new Handler() {
62         @Override
63         public void handleMessage(Message msg) {
64             AsyncResult ar = (AsyncResult) msg.obj;
65             Request request = (Request) ar.userObj;
66 
67             switch (msg.what) {
68                 case EVENT_GET_SIZE_DONE:
69                     int[] recordSize = null;
70                     if (ar.exception == null) {
71                         recordSize = (int[]) ar.result;
72                         // recordSize[0]  is the record length
73                         // recordSize[1]  is the total length of the EF file
74                         // recordSize[2]  is the number of records in the EF file
75                         logd("GET_RECORD_SIZE Size " + recordSize[0]
76                                 + " total " + recordSize[1]
77                                 + " #record " + recordSize[2]);
78                     } else {
79                         loge("EVENT_GET_SIZE_DONE: failed; ex=" + ar.exception);
80                     }
81                     notifyPending(request, recordSize);
82                     break;
83                 case EVENT_UPDATE_DONE:
84                     boolean success = (ar.exception == null);
85                     if (!success) {
86                         loge("EVENT_UPDATE_DONE - failed; ex=" + ar.exception);
87                     }
88                     notifyPending(request, success);
89                     break;
90                 case EVENT_LOAD_DONE:
91                     List<AdnRecord> records = null;
92                     if (ar.exception == null) {
93                         records = (List<AdnRecord>) ar.result;
94                     } else {
95                         loge("EVENT_LOAD_DONE: Cannot load ADN records; ex="
96                                 + ar.exception);
97                     }
98                     notifyPending(request, records);
99                     break;
100             }
101         }
102 
103         private void notifyPending(Request request, Object result) {
104             if (request != null) {
105                 synchronized (request) {
106                     request.mResult = result;
107                     request.mStatus.set(true);
108                     request.notifyAll();
109                 }
110             }
111         }
112     };
113 
IccPhoneBookInterfaceManager(Phone phone)114     public IccPhoneBookInterfaceManager(Phone phone) {
115         this.mPhone = phone;
116         IccRecords r = phone.getIccRecords();
117         if (r != null) {
118             mAdnCache = r.getAdnCache();
119         }
120     }
121 
dispose()122     public void dispose() {
123     }
124 
updateIccRecords(IccRecords iccRecords)125     public void updateIccRecords(IccRecords iccRecords) {
126         if (iccRecords != null) {
127             mAdnCache = iccRecords.getAdnCache();
128         } else {
129             mAdnCache = null;
130         }
131     }
132 
133     @UnsupportedAppUsage
logd(String msg)134     protected void logd(String msg) {
135         Rlog.d(LOG_TAG, "[IccPbInterfaceManager] " + msg);
136     }
137 
138     @UnsupportedAppUsage
loge(String msg)139     protected void loge(String msg) {
140         Rlog.e(LOG_TAG, "[IccPbInterfaceManager] " + msg);
141     }
142 
143     /**
144      * Replace oldAdn with newAdn in ADN-like record in EF
145      *
146      * getAdnRecordsInEf must be called at least once before this function,
147      * otherwise an error will be returned. Currently the email field
148      * if set in the ADN record is ignored.
149      * throws SecurityException if no WRITE_CONTACTS permission
150      *
151      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
152      * @param oldTag adn tag to be replaced
153      * @param oldPhoneNumber adn number to be replaced
154      *        Set both oldTag and oldPhoneNubmer to "" means to replace an
155      *        empty record, aka, insert new record
156      * @param newTag adn tag to be stored
157      * @param newPhoneNumber adn number ot be stored
158      *        Set both newTag and newPhoneNubmer to "" means to replace the old
159      *        record with empty one, aka, delete old record
160      * @param pin2 required to update EF_FDN, otherwise must be null
161      * @return true for success
162      */
163     public boolean
updateAdnRecordsInEfBySearch(int efid, String oldTag, String oldPhoneNumber, String newTag, String newPhoneNumber, String pin2)164     updateAdnRecordsInEfBySearch (int efid,
165             String oldTag, String oldPhoneNumber,
166             String newTag, String newPhoneNumber, String pin2) {
167 
168 
169         if (mPhone.getContext().checkCallingOrSelfPermission(
170                 android.Manifest.permission.WRITE_CONTACTS)
171             != PackageManager.PERMISSION_GRANTED) {
172             throw new SecurityException(
173                     "Requires android.permission.WRITE_CONTACTS permission");
174         }
175 
176 
177         if (DBG) logd("updateAdnRecordsInEfBySearch: efid=0x" +
178                 Integer.toHexString(efid).toUpperCase() + " ("+ Rlog.pii(LOG_TAG, oldTag) + "," +
179                 Rlog.pii(LOG_TAG, oldPhoneNumber) + ")" + "==>" + " ("+ Rlog.pii(LOG_TAG, newTag) +
180                 "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")"+ " pin2=" + Rlog.pii(LOG_TAG, pin2));
181 
182         efid = updateEfForIccType(efid);
183 
184         checkThread();
185         Request updateRequest = new Request();
186         synchronized (updateRequest) {
187             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
188             AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber);
189             AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
190             if (mAdnCache != null) {
191                 mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
192                 waitForResult(updateRequest);
193                 return (boolean) updateRequest.mResult;
194             } else {
195                 loge("Failure while trying to update by search due to uninitialised adncache");
196                 return false;
197             }
198         }
199     }
200 
201     /**
202      * Update an ADN-like EF record by record index
203      *
204      * This is useful for iteration the whole ADN file, such as write the whole
205      * phone book or erase/format the whole phonebook. Currently the email field
206      * if set in the ADN record is ignored.
207      * throws SecurityException if no WRITE_CONTACTS permission
208      *
209      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
210      * @param newTag adn tag to be stored
211      * @param newPhoneNumber adn number to be stored
212      *        Set both newTag and newPhoneNubmer to "" means to replace the old
213      *        record with empty one, aka, delete old record
214      * @param index is 1-based adn record index to be updated
215      * @param pin2 required to update EF_FDN, otherwise must be null
216      * @return true for success
217      */
218     public boolean
updateAdnRecordsInEfByIndex(int efid, String newTag, String newPhoneNumber, int index, String pin2)219     updateAdnRecordsInEfByIndex(int efid, String newTag,
220             String newPhoneNumber, int index, String pin2) {
221 
222         if (mPhone.getContext().checkCallingOrSelfPermission(
223                 android.Manifest.permission.WRITE_CONTACTS)
224                 != PackageManager.PERMISSION_GRANTED) {
225             throw new SecurityException(
226                     "Requires android.permission.WRITE_CONTACTS permission");
227         }
228 
229         if (DBG) logd("updateAdnRecordsInEfByIndex: efid=0x" +
230                 Integer.toHexString(efid).toUpperCase() + " Index=" + index + " ==> " + "(" +
231                 Rlog.pii(LOG_TAG, newTag) + "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")" +
232                 " pin2=" + Rlog.pii(LOG_TAG, pin2));
233 
234 
235         checkThread();
236         Request updateRequest = new Request();
237         synchronized (updateRequest) {
238             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
239             AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
240             if (mAdnCache != null) {
241                 mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
242                 waitForResult(updateRequest);
243                 return (boolean) updateRequest.mResult;
244             } else {
245                 loge("Failure while trying to update by index due to uninitialised adncache");
246                 return false;
247             }
248         }
249     }
250 
251     /**
252      * Get the capacity of records in efid
253      *
254      * @param efid the EF id of a ADN-like ICC
255      * @return  int[3] array
256      *            recordSizes[0]  is the single record length
257      *            recordSizes[1]  is the total length of the EF file
258      *            recordSizes[2]  is the number of records in the EF file
259      */
getAdnRecordsSize(int efid)260     public int[] getAdnRecordsSize(int efid) {
261         if (DBG) logd("getAdnRecordsSize: efid=" + efid);
262         checkThread();
263         Request getSizeRequest = new Request();
264         synchronized (getSizeRequest) {
265             //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling
266             Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, getSizeRequest);
267             IccFileHandler fh = mPhone.getIccFileHandler();
268             if (fh != null) {
269                 fh.getEFLinearRecordSize(efid, response);
270                 waitForResult(getSizeRequest);
271             }
272         }
273 
274         return getSizeRequest.mResult == null ? new int[3] : (int[]) getSizeRequest.mResult;
275     }
276 
277 
278     /**
279      * Loads the AdnRecords in efid and returns them as a
280      * List of AdnRecords
281      *
282      * throws SecurityException if no READ_CONTACTS permission
283      *
284      * @param efid the EF id of a ADN-like ICC
285      * @return List of AdnRecord
286      */
getAdnRecordsInEf(int efid)287     public List<AdnRecord> getAdnRecordsInEf(int efid) {
288 
289         if (mPhone.getContext().checkCallingOrSelfPermission(
290                 android.Manifest.permission.READ_CONTACTS)
291                 != PackageManager.PERMISSION_GRANTED) {
292             throw new SecurityException(
293                     "Requires android.permission.READ_CONTACTS permission");
294         }
295 
296         efid = updateEfForIccType(efid);
297         if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase());
298 
299         checkThread();
300         Request loadRequest = new Request();
301         synchronized (loadRequest) {
302             Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest);
303             if (mAdnCache != null) {
304                 mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response);
305                 waitForResult(loadRequest);
306                 return (List<AdnRecord>) loadRequest.mResult;
307             } else {
308                 loge("Failure while trying to load from SIM due to uninitialised adncache");
309                 return null;
310             }
311         }
312     }
313 
314     @UnsupportedAppUsage
checkThread()315     protected void checkThread() {
316         // Make sure this isn't the UI thread, since it will block
317         if (mBaseHandler.getLooper().equals(Looper.myLooper())) {
318             loge("query() called on the main UI thread!");
319             throw new IllegalStateException(
320                     "You cannot call query on this provder from the main UI thread.");
321         }
322     }
323 
waitForResult(Request request)324     protected void waitForResult(Request request) {
325         synchronized (request) {
326             while (!request.mStatus.get()) {
327                 try {
328                     request.wait();
329                 } catch (InterruptedException e) {
330                     logd("interrupted while trying to update by search");
331                 }
332             }
333         }
334     }
335 
336     @UnsupportedAppUsage
updateEfForIccType(int efid)337     private int updateEfForIccType(int efid) {
338         // Check if we are trying to read ADN records
339         if (efid == IccConstants.EF_ADN) {
340             if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) {
341                 return IccConstants.EF_PBR;
342             }
343         }
344         return efid;
345     }
346 }
347 
348