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.os.ServiceManager;
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.IccRecords;
31import com.android.internal.telephony.uicc.UiccCard;
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 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
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
122    public void dispose() {
123    }
124
125    public void updateIccRecords(IccRecords iccRecords) {
126        if (iccRecords != null) {
127            mAdnCache = iccRecords.getAdnCache();
128        } else {
129            mAdnCache = null;
130        }
131    }
132
133    protected abstract void logd(String msg);
134
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
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
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     */
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     */
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
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
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
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