1/*
2 * Copyright (C) 2011 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.uicc;
18
19import android.content.Context;
20import android.os.AsyncResult;
21import android.os.Handler;
22import android.os.Message;
23import android.telephony.Rlog;
24
25import com.android.internal.telephony.CommandsInterface;
26import com.android.internal.telephony.gsm.SimTlv;
27//import com.android.internal.telephony.gsm.VoiceMailConstants;
28
29import java.io.FileDescriptor;
30import java.io.PrintWriter;
31import java.nio.charset.Charset;
32import java.util.ArrayList;
33import java.util.Arrays;
34
35import static com.android.internal.telephony.uicc.IccConstants.EF_DOMAIN;
36import static com.android.internal.telephony.uicc.IccConstants.EF_IMPI;
37import static com.android.internal.telephony.uicc.IccConstants.EF_IMPU;
38
39/**
40 * {@hide}
41 */
42public final class IsimUiccRecords extends IccRecords implements IsimRecords {
43    protected static final String LOG_TAG = "IsimUiccRecords";
44
45    private static final boolean DBG = true;
46    private static final boolean DUMP_RECORDS = false;   // Note: PII is logged when this is true
47
48    private static final int EVENT_APP_READY = 1;
49
50    // ISIM EF records (see 3GPP TS 31.103)
51    private String mIsimImpi;               // IMS private user identity
52    private String mIsimDomain;             // IMS home network domain name
53    private String[] mIsimImpu;             // IMS public user identity(s)
54
55    private static final int TAG_ISIM_VALUE = 0x80;     // From 3GPP TS 31.103
56
57    @Override
58    public String toString() {
59        return "IsimUiccRecords: " + super.toString()
60                + " mIsimImpi=" + mIsimImpi
61                + " mIsimDomain=" + mIsimDomain
62                + " mIsimImpu=" + mIsimImpu;
63    }
64
65    public IsimUiccRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
66        super(app, c, ci);
67
68        mRecordsRequested = false;  // No load request is made till SIM ready
69
70        // recordsToLoad is set to 0 because no requests are made yet
71        mRecordsToLoad = 0;
72
73        mParentApp.registerForReady(this, EVENT_APP_READY, null);
74        if (DBG) log("IsimUiccRecords X ctor this=" + this);
75    }
76
77    @Override
78    public void dispose() {
79        log("Disposing " + this);
80        //Unregister for all events
81        mParentApp.unregisterForReady(this);
82        resetRecords();
83        super.dispose();
84    }
85
86    // ***** Overridden from Handler
87    public void handleMessage(Message msg) {
88        if (mDestroyed.get()) {
89            Rlog.e(LOG_TAG, "Received message " + msg +
90                    "[" + msg.what + "] while being destroyed. Ignoring.");
91            return;
92        }
93
94        try {
95            switch (msg.what) {
96                case EVENT_APP_READY:
97                    onReady();
98                    break;
99
100                default:
101                    super.handleMessage(msg);   // IccRecords handles generic record load responses
102
103            }
104        } catch (RuntimeException exc) {
105            // I don't want these exceptions to be fatal
106            Rlog.w(LOG_TAG, "Exception parsing SIM record", exc);
107        }
108    }
109
110    protected void fetchIsimRecords() {
111        mRecordsRequested = true;
112
113        mFh.loadEFTransparent(EF_IMPI, obtainMessage(
114                IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
115        mRecordsToLoad++;
116
117        mFh.loadEFLinearFixedAll(EF_IMPU, obtainMessage(
118                IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
119        mRecordsToLoad++;
120
121        mFh.loadEFTransparent(EF_DOMAIN, obtainMessage(
122                IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
123        mRecordsToLoad++;
124
125        log("fetchIsimRecords " + mRecordsToLoad);
126    }
127
128    protected void resetRecords() {
129        // recordsRequested is set to false indicating that the SIM
130        // read requests made so far are not valid. This is set to
131        // true only when fresh set of read requests are made.
132        mRecordsRequested = false;
133    }
134
135    private class EfIsimImpiLoaded implements IccRecords.IccRecordLoaded {
136        public String getEfName() {
137            return "EF_ISIM_IMPI";
138        }
139        public void onRecordLoaded(AsyncResult ar) {
140            byte[] data = (byte[]) ar.result;
141            mIsimImpi = isimTlvToString(data);
142            if (DUMP_RECORDS) log("EF_IMPI=" + mIsimImpi);
143        }
144    }
145
146    private class EfIsimImpuLoaded implements IccRecords.IccRecordLoaded {
147        public String getEfName() {
148            return "EF_ISIM_IMPU";
149        }
150        public void onRecordLoaded(AsyncResult ar) {
151            ArrayList<byte[]> impuList = (ArrayList<byte[]>) ar.result;
152            if (DBG) log("EF_IMPU record count: " + impuList.size());
153            mIsimImpu = new String[impuList.size()];
154            int i = 0;
155            for (byte[] identity : impuList) {
156                String impu = isimTlvToString(identity);
157                if (DUMP_RECORDS) log("EF_IMPU[" + i + "]=" + impu);
158                mIsimImpu[i++] = impu;
159            }
160        }
161    }
162
163    private class EfIsimDomainLoaded implements IccRecords.IccRecordLoaded {
164        public String getEfName() {
165            return "EF_ISIM_DOMAIN";
166        }
167        public void onRecordLoaded(AsyncResult ar) {
168            byte[] data = (byte[]) ar.result;
169            mIsimDomain = isimTlvToString(data);
170            if (DUMP_RECORDS) log("EF_DOMAIN=" + mIsimDomain);
171        }
172    }
173
174    /**
175     * ISIM records for IMS are stored inside a Tag-Length-Value record as a UTF-8 string
176     * with tag value 0x80.
177     * @param record the byte array containing the IMS data string
178     * @return the decoded String value, or null if the record can't be decoded
179     */
180    private static String isimTlvToString(byte[] record) {
181        SimTlv tlv = new SimTlv(record, 0, record.length);
182        do {
183            if (tlv.getTag() == TAG_ISIM_VALUE) {
184                return new String(tlv.getData(), Charset.forName("UTF-8"));
185            }
186        } while (tlv.nextObject());
187
188        Rlog.e(LOG_TAG, "[ISIM] can't find TLV tag in ISIM record, returning null");
189        return null;
190    }
191
192    @Override
193    protected void onRecordLoaded() {
194        // One record loaded successfully or failed, In either case
195        // we need to update the recordsToLoad count
196        mRecordsToLoad -= 1;
197
198        if (mRecordsToLoad == 0 && mRecordsRequested == true) {
199            onAllRecordsLoaded();
200        } else if (mRecordsToLoad < 0) {
201            loge("recordsToLoad <0, programmer error suspected");
202            mRecordsToLoad = 0;
203        }
204    }
205
206    @Override
207    protected void onAllRecordsLoaded() {
208        mRecordsLoadedRegistrants.notifyRegistrants(
209                new AsyncResult(null, null, null));
210    }
211
212    /**
213     * Return the IMS private user identity (IMPI).
214     * Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
215     * @return the IMS private user identity string, or null if not available
216     */
217    @Override
218    public String getIsimImpi() {
219        return mIsimImpi;
220    }
221
222    /**
223     * Return the IMS home network domain name.
224     * Returns null if the IMS domain hasn't been loaded or isn't present on the ISIM.
225     * @return the IMS home network domain name, or null if not available
226     */
227    @Override
228    public String getIsimDomain() {
229        return mIsimDomain;
230    }
231
232    /**
233     * Return an array of IMS public user identities (IMPU).
234     * Returns null if the IMPU hasn't been loaded or isn't present on the ISIM.
235     * @return an array of IMS public user identity strings, or null if not available
236     */
237    @Override
238    public String[] getIsimImpu() {
239        return (mIsimImpu != null) ? mIsimImpu.clone() : null;
240    }
241
242    @Override
243    public int getDisplayRule(String plmn) {
244        // Not applicable to Isim
245        return 0;
246    }
247
248    @Override
249    public void onReady() {
250        fetchIsimRecords();
251    }
252
253    @Override
254    public void onRefresh(boolean fileChanged, int[] fileList) {
255        // We do not handle it in Isim
256    }
257
258    @Override
259    public void setVoiceMailNumber(String alphaTag, String voiceNumber,
260            Message onComplete) {
261        // Not applicable to Isim
262    }
263
264    @Override
265    public void setVoiceMessageWaiting(int line, int countWaiting) {
266        // Not applicable to Isim
267    }
268
269    @Override
270    protected void log(String s) {
271        if (DBG) Rlog.d(LOG_TAG, "[ISIM] " + s);
272    }
273
274    @Override
275    protected void loge(String s) {
276        if (DBG) Rlog.e(LOG_TAG, "[ISIM] " + s);
277    }
278
279    @Override
280    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
281        pw.println("IsimRecords: " + this);
282        pw.println(" extends:");
283        super.dump(fd, pw, args);
284        pw.println(" mIsimImpi=" + mIsimImpi);
285        pw.println(" mIsimDomain=" + mIsimDomain);
286        pw.println(" mIsimImpu[]=" + Arrays.toString(mIsimImpu));
287        pw.flush();
288    }
289}
290