1/*
2 * Copyright (C) 2014 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.services.telephony;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.net.Uri;
25import android.telecom.PhoneAccount;
26import android.telecom.PhoneAccountHandle;
27import android.telecom.TelecomManager;
28import android.telephony.PhoneStateListener;
29import android.telephony.ServiceState;
30import android.telephony.SubInfoRecord;
31import android.telephony.SubscriptionManager;
32import android.telephony.TelephonyManager;
33import android.text.TextUtils;
34
35import com.android.internal.telephony.Phone;
36import com.android.internal.telephony.PhoneFactory;
37import com.android.internal.telephony.PhoneProxy;
38import com.android.internal.telephony.TelephonyIntents;
39import com.android.phone.R;
40
41import java.util.Arrays;
42import java.util.LinkedList;
43import java.util.List;
44
45/**
46 * Owns all data we have registered with Telecom including handling dynamic addition and
47 * removal of SIMs and SIP accounts.
48 */
49final class TelecomAccountRegistry {
50    private static final boolean DBG = false; /* STOP SHIP if true */
51
52    // Slot IDs are zero based indices but the numbered icons represent the first, second,
53    // etc... SIM in the device. So that means that index 0 is SIM 1, index 1 is SIM 2 and so on.
54
55    private final static int[] phoneAccountIcons = {
56            R.drawable.ic_multi_sim1,
57            R.drawable.ic_multi_sim2,
58            R.drawable.ic_multi_sim3,
59            R.drawable.ic_multi_sim4
60    };
61
62    // This icon is the one that is used when the Slot ID that we have for a particular SIM
63    // is not supported, i.e. SubscriptionManager.INVALID_SLOT_ID or the 5th SIM in a phone.
64    private final static int defaultPhoneAccountIcon =  R.drawable.ic_multi_sim;
65
66    private final class AccountEntry {
67        private final Phone mPhone;
68        private final PhoneAccount mAccount;
69        private final PstnIncomingCallNotifier mIncomingCallNotifier;
70
71        AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
72            mPhone = phone;
73            mAccount = registerPstnPhoneAccount(isEmergency, isDummy);
74            Log.d(this, "Registered phoneAccount: %s with handle: %s",
75                    mAccount, mAccount.getAccountHandle());
76            mIncomingCallNotifier = new PstnIncomingCallNotifier((PhoneProxy) mPhone);
77        }
78
79        void teardown() {
80            mIncomingCallNotifier.teardown();
81        }
82
83        /**
84         * Registers the specified account with Telecom as a PhoneAccountHandle.
85         */
86        private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) {
87            String dummyPrefix = isDummyAccount ? "Dummy " : "";
88
89            // Build the Phone account handle.
90            PhoneAccountHandle phoneAccountHandle =
91                    makePstnPhoneAccountHandleWithPrefix(mPhone, dummyPrefix, isEmergency);
92
93            // Populate the phone account data.
94            long subId = mPhone.getSubId();
95            int slotId = SubscriptionManager.INVALID_SLOT_ID;
96            String line1Number = mTelephonyManager.getLine1NumberForSubscriber(subId);
97            if (line1Number == null) {
98                line1Number = "";
99            }
100            String subNumber = mPhone.getPhoneSubInfo().getLine1Number();
101            if (subNumber == null) {
102                subNumber = "";
103            }
104
105            String label;
106            String description;
107
108            if (isEmergency) {
109                label = mContext.getResources().getString(R.string.sim_label_emergency_calls);
110                description =
111                        mContext.getResources().getString(R.string.sim_description_emergency_calls);
112            } else if (mTelephonyManager.getPhoneCount() == 1) {
113                // For single-SIM devices, we show the label and description as whatever the name of
114                // the network is.
115                description = label = mTelephonyManager.getNetworkOperatorName();
116            } else {
117                String subDisplayName = null;
118                // We can only get the real slotId from the SubInfoRecord, we can't calculate the
119                // slotId from the subId or the phoneId in all instances.
120                SubInfoRecord record = SubscriptionManager.getSubInfoForSubscriber(subId);
121                if (record != null) {
122                    subDisplayName = record.displayName;
123                    slotId = record.slotId;
124                }
125
126                String slotIdString;
127                if (SubscriptionManager.isValidSlotId(slotId)) {
128                    slotIdString = Integer.toString(slotId);
129                } else {
130                    slotIdString = mContext.getResources().getString(R.string.unknown);
131                }
132
133                if (TextUtils.isEmpty(subDisplayName)) {
134                    // Either the sub record is not there or it has an empty display name.
135                    Log.w(this, "Could not get a display name for subid: %d", subId);
136                    subDisplayName = mContext.getResources().getString(
137                            R.string.sim_description_default, slotIdString);
138                }
139
140                // The label is user-visible so let's use the display name that the user may
141                // have set in Settings->Sim cards.
142                label = dummyPrefix + subDisplayName;
143                description = dummyPrefix + mContext.getResources().getString(
144                                R.string.sim_description_default, slotIdString);
145            }
146
147            // By default all SIM phone accounts can place emergency calls.
148            int capabilities = PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
149                    PhoneAccount.CAPABILITY_CALL_PROVIDER |
150                    PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
151
152            PhoneAccount account = PhoneAccount.builder(phoneAccountHandle, label)
153                    .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, line1Number, null))
154                    .setSubscriptionAddress(
155                            Uri.fromParts(PhoneAccount.SCHEME_TEL, subNumber, null))
156                    .setCapabilities(capabilities)
157                    .setIconResId(getPhoneAccountIcon(slotId))
158                    .setShortDescription(description)
159                    .setSupportedUriSchemes(Arrays.asList(
160                            PhoneAccount.SCHEME_TEL, PhoneAccount.SCHEME_VOICEMAIL))
161                    .build();
162
163            // Register with Telecom and put into the account entry.
164            mTelecomManager.registerPhoneAccount(account);
165            return account;
166        }
167
168        public PhoneAccountHandle getPhoneAccountHandle() {
169            return mAccount != null ? mAccount.getAccountHandle() : null;
170        }
171    }
172
173    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
174        @Override
175        public void onReceive(Context context, Intent intent) {
176            boolean rebuildAccounts = false;
177            String action = intent.getAction();
178            if (TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED.equals(action)) {
179                int status = intent.getIntExtra(
180                        SubscriptionManager.INTENT_KEY_DETECT_STATUS,
181                        SubscriptionManager.EXTRA_VALUE_NOCHANGE);
182                Log.i(this, "SUBINFO_RECORD_UPDATED : %d.", status);
183                // Anytime the SIM state changes...rerun the setup
184                // We rely on this notification even when the status is EXTRA_VALUE_NOCHANGE,
185                // so we explicitly do not check for that here.
186                rebuildAccounts = true;
187            } else if (TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE.equals(action)) {
188                String columnName = intent.getStringExtra(TelephonyIntents.EXTRA_COLUMN_NAME);
189                String stringContent = intent.getStringExtra(TelephonyIntents.EXTRA_STRING_CONTENT);
190                Log.v(this, "SUBINFO_CONTENT_CHANGE: Column: %s Content: %s",
191                        columnName, stringContent);
192                rebuildAccounts = true;
193            }
194            if (rebuildAccounts) {
195                tearDownAccounts();
196                setupAccounts();
197            }
198        }
199    };
200
201    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
202        @Override
203        public void onServiceStateChanged(ServiceState serviceState) {
204            int newState = serviceState.getState();
205            if (newState == ServiceState.STATE_IN_SERVICE && mServiceState != newState) {
206                tearDownAccounts();
207                setupAccounts();
208            }
209            mServiceState = newState;
210        }
211    };
212
213    private static TelecomAccountRegistry sInstance;
214    private final Context mContext;
215    private final TelecomManager mTelecomManager;
216    private final TelephonyManager mTelephonyManager;
217    private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
218    private int mServiceState = ServiceState.STATE_POWER_OFF;
219
220    TelecomAccountRegistry(Context context) {
221        mContext = context;
222        mTelecomManager = TelecomManager.from(context);
223        mTelephonyManager = TelephonyManager.from(context);
224    }
225
226    static synchronized final TelecomAccountRegistry getInstance(Context context) {
227        if (sInstance == null) {
228            sInstance = new TelecomAccountRegistry(context);
229        }
230        return sInstance;
231    }
232
233    /**
234     * Sets up all the phone accounts for SIMs on first boot.
235     */
236    void setupOnBoot() {
237        // We need to register for both types of intents if we want to see added/removed Subs
238        // along with changes to a given Sub.
239        IntentFilter intentFilter = new IntentFilter();
240        intentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
241        intentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
242        mContext.registerReceiver(mReceiver, intentFilter);
243
244        // We also need to listen for changes to the service state (e.g. emergency -> in service)
245        // because this could signal a removal or addition of a SIM in a single SIM phone.
246        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
247    }
248
249    static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
250        return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
251    }
252
253    private static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
254            Phone phone, String prefix, boolean isEmergency) {
255        ComponentName pstnConnectionServiceName =
256                new ComponentName(phone.getContext(), TelephonyConnectionService.class);
257        // TODO: Should use some sort of special hidden flag to decorate this account as
258        // an emergency-only account
259        String id = isEmergency ? "E" : prefix + String.valueOf(phone.getSubId());
260        return new PhoneAccountHandle(pstnConnectionServiceName, id);
261    }
262
263    /**
264     * Determines if the list of {@link AccountEntry}(s) contains an {@link AccountEntry} with a
265     * specified {@link PhoneAccountHandle}.
266     *
267     * @param handle The {@link PhoneAccountHandle}.
268     * @return {@code True} if an entry exists.
269     */
270    private boolean hasAccountEntryForPhoneAccount(PhoneAccountHandle handle) {
271        for (AccountEntry entry : mAccounts) {
272            if (entry.getPhoneAccountHandle().equals(handle)) {
273                return true;
274            }
275        }
276        return false;
277    }
278
279    /**
280     * Un-registers any {@link PhoneAccount}s which are no longer present in the list
281     * {@code AccountEntry}(s).
282     */
283    private void cleanupPhoneAccounts() {
284        ComponentName telephonyComponentName =
285                new ComponentName(mContext, TelephonyConnectionService.class);
286        List<PhoneAccountHandle> accountHandles = mTelecomManager.getAllPhoneAccountHandles();
287        for (PhoneAccountHandle handle : accountHandles) {
288            if (telephonyComponentName.equals(handle.getComponentName()) &&
289                    !hasAccountEntryForPhoneAccount(handle)) {
290                Log.d(this, "Unregistering phone account %s.", handle);
291                mTelecomManager.unregisterPhoneAccount(handle);
292            }
293        }
294    }
295
296    private void setupAccounts() {
297        // Go through SIM-based phones and register ourselves -- registering an existing account
298        // will cause the existing entry to be replaced.
299        Phone[] phones = PhoneFactory.getPhones();
300        Log.d(this, "Found %d phones.  Attempting to register.", phones.length);
301        for (Phone phone : phones) {
302            long subscriptionId = phone.getSubId();
303            Log.d(this, "Phone with subscription id %d", subscriptionId);
304            if (subscriptionId >= 0) {
305                mAccounts.add(new AccountEntry(phone, false /* emergency */, false /* isDummy */));
306            }
307        }
308
309        // If we did not list ANY accounts, we need to provide a "default" SIM account
310        // for emergency numbers since no actual SIM is needed for dialing emergency
311        // numbers but a phone account is.
312        if (mAccounts.isEmpty()) {
313            mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
314                    false /* isDummy */));
315        }
316
317        // Add a fake account entry.
318        if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
319            mAccounts.add(new AccountEntry(phones[0], false /* emergency */, true /* isDummy */));
320        }
321
322        // Clean up any PhoneAccounts that are no longer relevant
323        cleanupPhoneAccounts();
324    }
325
326    private int getPhoneAccountIcon(int index) {
327        // A valid slot id doesn't necessarily mean that we have an icon for it.
328        if (SubscriptionManager.isValidSlotId(index) &&
329                index < TelecomAccountRegistry.phoneAccountIcons.length) {
330            return TelecomAccountRegistry.phoneAccountIcons[index];
331        }
332        // Invalid indices get the default icon that has no number associated with it.
333        return defaultPhoneAccountIcon;
334    }
335
336    private void tearDownAccounts() {
337        for (AccountEntry entry : mAccounts) {
338            entry.teardown();
339        }
340        mAccounts.clear();
341    }
342}
343