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