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