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
17package com.android.internal.telephony;
18
19import android.content.pm.PackageManager;
20import android.os.AsyncResult;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.telephony.Rlog;
25
26import com.android.internal.telephony.uicc.AdnRecord;
27import com.android.internal.telephony.uicc.AdnRecordCache;
28import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
29import com.android.internal.telephony.uicc.IccConstants;
30import com.android.internal.telephony.uicc.IccFileHandler;
31import com.android.internal.telephony.uicc.IccRecords;
32import com.android.internal.telephony.uicc.UiccCardApplication;
33
34import java.util.List;
35import java.util.concurrent.atomic.AtomicBoolean;
36
37/**
38 * SimPhoneBookInterfaceManager to provide an inter-process communication to
39 * access ADN-like SIM records.
40 */
41public 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
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
121    public void dispose() {
122    }
123
124    public void updateIccRecords(IccRecords iccRecords) {
125        if (iccRecords != null) {
126            mAdnCache = iccRecords.getAdnCache();
127        } else {
128            mAdnCache = null;
129        }
130    }
131
132    protected void logd(String msg) {
133        Rlog.d(LOG_TAG, "[IccPbInterfaceManager] " + msg);
134    }
135
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
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() + " ("+ Rlog.pii(LOG_TAG, oldTag) + "," +
176                Rlog.pii(LOG_TAG, oldPhoneNumber) + ")" + "==>" + " ("+ Rlog.pii(LOG_TAG, newTag) +
177                "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")"+ " pin2=" + Rlog.pii(LOG_TAG, 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
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() + " Index=" + index + " ==> " + "(" +
228                Rlog.pii(LOG_TAG, newTag) + "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")" +
229                " pin2=" + Rlog.pii(LOG_TAG, 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     */
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     */
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
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
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
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