Fix initialization of SIM accounts.

- Moves setup out of a broadcast receiver and into TelephonyGlobals
- Reruns setup whenever the SIM state changes (hotswap support)
- When no SIM is inserted, register an emergency-only PhoneAccount.

Bug: 16491446
Bug: 16445149
Bug: 16495971

Change-Id: Icee8013c35f7439c58af7b7777af9a99798bff43
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cdc6a4c..da9da33 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -585,13 +585,5 @@
                 <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
             </intent-filter>
         </provider>
-
-        <receiver android:name="com.android.services.telephony.AddAccountsReceiver"
-                 android:singleUser="true" >
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </receiver>
     </application>
 </manifest>
diff --git a/src/com/android/services/telephony/AddAccountsReceiver.java b/src/com/android/services/telephony/AddAccountsReceiver.java
deleted file mode 100644
index f106e7d..0000000
--- a/src/com/android/services/telephony/AddAccountsReceiver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.services.telephony;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class AddAccountsReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-        Log.d(this, "onReceive(%s)", action);
-        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-            TelecommAccountRegistry.getInstance(context).setup();
-        }
-    }
-}
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index edf1b2b..183cbb0 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -103,6 +103,11 @@
         mPhoneProxy.getContext().registerReceiver(mRATReceiver, intentFilter);
     }
 
+    void teardown() {
+        unregisterForNotifications();
+        mPhoneProxy.getContext().unregisterReceiver(mRATReceiver);
+    }
+
     /**
      * Register for notifications from the base phone.
      * TODO(santoscordon): We should only need to interact with the phoneproxy directly. However,
@@ -130,6 +135,12 @@
         }
     }
 
+    private void unregisterForNotifications() {
+        if (mPhoneBase != null) {
+            mPhoneBase.unregisterForNewRingingConnection(mHandler);
+        }
+    }
+
     /**
      * Verifies the incoming call and triggers sending the incoming-call intent to Telecomm.
      *
diff --git a/src/com/android/services/telephony/TelecommAccountRegistry.java b/src/com/android/services/telephony/TelecommAccountRegistry.java
index cef546d..78aa057 100644
--- a/src/com/android/services/telephony/TelecommAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecommAccountRegistry.java
@@ -16,8 +16,11 @@
 
 package com.android.services.telephony;
 
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.Uri;
 import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
@@ -27,6 +30,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.TelephonyIntents;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -36,30 +40,36 @@
  * removal of SIMs and SIP accounts.
  */
 final class TelecommAccountRegistry {
+
     private final class AccountEntry {
         private final Phone mPhone;
         private final PhoneAccount mAccount;
         private final PstnIncomingCallNotifier mIncomingCallNotifier;
 
-        AccountEntry(Phone phone, boolean isDummy) {
+        AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
             mPhone = phone;
-            mAccount = registerPstnPhoneAccount(isDummy);
+            mAccount = registerPstnPhoneAccount(isEmergency, isDummy);
             Log.d(this, "Registered phoneAccount: %s with handle: %s",
                     mAccount, mAccount.getAccountHandle());
             mIncomingCallNotifier = new PstnIncomingCallNotifier((PhoneProxy) mPhone);
         }
 
+        void teardown() {
+            mTelecommManager.unregisterPhoneAccount(mAccount.getAccountHandle());
+            mIncomingCallNotifier.teardown();
+        }
+
         /**
          * Registers the specified account with Telecomm as a PhoneAccountHandle.
          */
-        private PhoneAccount registerPstnPhoneAccount(boolean isDummyAccount) {
+        private PhoneAccount registerPstnPhoneAccount(
+                boolean isEmergency, boolean isDummyAccount) {
             TelephonyManager telephonyManager = TelephonyManager.from(mContext);
             String dummyPrefix = isDummyAccount ? "Dummy " : "";
 
             // Build the Phone account handle.
-            PhoneAccountHandle phoneAccountHandle = isDummyAccount ?
-                    makePstnPhoneAccountHandleWithPrefix(mPhone, dummyPrefix) :
-                    makePstnPhoneAccountHandle(mPhone);
+            PhoneAccountHandle phoneAccountHandle =
+                    makePstnPhoneAccountHandleWithPrefix(mPhone, dummyPrefix, isEmergency);
 
             // Populate the phone account data.
             long subId = mPhone.getSubId();
@@ -68,15 +78,22 @@
             if (line1Number == null) {
                 line1Number = "";
             }
+            String subNumber = isEmergency ? "" : mPhone.getPhoneSubInfo().getLine1Number();
+            String label = isEmergency
+                    ? "Emergency calls"
+                    : dummyPrefix + "SIM " + slotId;
+            String description = isEmergency
+                    ? "Emergency calling only"
+                    : dummyPrefix + "SIM card in slot " + slotId;
             PhoneAccount account = new PhoneAccount(
                     phoneAccountHandle,
                     Uri.fromParts(TEL_SCHEME, line1Number, null),
-                    mPhone.getPhoneSubInfo().getLine1Number(),
+                    subNumber,
                     PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
                             PhoneAccount.CAPABILITY_CALL_PROVIDER,
                     com.android.phone.R.mipmap.ic_launcher_phone,
-                    dummyPrefix + "SIM " + slotId,
-                    dummyPrefix + "SIM card in slot " + slotId,
+                    label,
+                    description,
                     true /* supportsVideoCalling */);
 
             // Register with Telecomm and put into the account entry.
@@ -85,6 +102,19 @@
         }
     }
 
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+                Log.d(this, "SIM_STATE_CHANGED - rerun setup");
+                // Anytime the SIM state changes...rerun the setup.
+                tearDownAccounts();
+                setupAccounts();
+            }
+        }
+    };
+
     private static final String TEL_SCHEME = "tel";
     private static TelecommAccountRegistry sInstance;
     private final Context mContext;
@@ -106,9 +136,29 @@
     /**
      * Sets up all the phone accounts for SIM and SIP accounts on first boot.
      */
-    void setup() {
-        // Initialize the PhoneFactory, since the PhoneApp may not yet have been set up
-        PhoneFactory.makeDefaultPhones(mContext);
+    void setupOnBoot() {
+        IntentFilter intentFilter =
+            new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        mContext.registerReceiver(mReceiver, intentFilter);
+
+        setupAccounts();
+    }
+
+    static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
+        return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
+    }
+
+    private static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
+            Phone phone, String prefix, boolean isEmergency) {
+        ComponentName pstnConnectionServiceName =
+                new ComponentName(phone.getContext(), TelephonyConnectionService.class);
+        // TODO: Should use some sort of special hidden flag to decorate this account as
+        // an emergency-only account
+        String id = isEmergency ? "E" : prefix + String.valueOf(phone.getSubId());
+        return new PhoneAccountHandle(pstnConnectionServiceName, id);
+    }
+
+    private void setupAccounts() {
         // Before we do anything, we need to clear whatever entries we registered at boot.
         mTelecommManager.clearAccounts(mContext.getPackageName());
 
@@ -119,27 +169,30 @@
             long subscriptionId = phone.getSubId();
             Log.d(this, "Phone with subscription id %d", subscriptionId);
             if (subscriptionId >= 0) {
-                mAccounts.add(new AccountEntry(phone, false /* isDummy */));
+                mAccounts.add(new AccountEntry(phone, false, false /* isDummy */));
             }
         }
 
+        // If we did not list ANY accounts, we need to provide a "default" SIM account
+        // for emergency numbers since no actual SIM is needed for dialing emergency
+        // numbers but a phone account is.
+        if (mAccounts.isEmpty()) {
+            mAccounts.add(new AccountEntry(
+                    PhoneFactory.getDefaultPhone(), true /*emergency*/, false /*isDummy*/));
+        }
+
         // Add a fake account entry.
         if (phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
-            mAccounts.add(new AccountEntry(phones[0], true /* isDummy */));
+            mAccounts.add(new AccountEntry(phones[0], false, true /* isDummy */));
         }
 
         // TODO: Add SIP accounts.
     }
 
-    static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
-        return makePstnPhoneAccountHandleWithPrefix(phone, "");
-    }
-
-    private static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
-            Phone phone, String prefix) {
-        ComponentName pstnConnectionServiceName =
-                new ComponentName(phone.getContext(), TelephonyConnectionService.class);
-        return new PhoneAccountHandle(
-                pstnConnectionServiceName, prefix + String.valueOf(phone.getSubId()));
+    private void tearDownAccounts() {
+        for (AccountEntry entry : mAccounts) {
+            entry.teardown();
+        }
+        mAccounts.clear();
     }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 00fc362..513692f 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -33,7 +33,6 @@
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Phone;
-
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SubscriptionController;
@@ -83,15 +82,16 @@
             return;
         }
 
+        boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number);
+
         // Get the right phone object from the account data passed in.
-        final Phone phone = getPhoneForAccount(request.getAccountHandle());
+        final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
         if (phone == null) {
             Log.d(this, "onCreateOutgoingConnection, phone is null");
             response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Phone is null");
             return;
         }
 
-        boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number);
         if (!isEmergencyNumber) {
             int state = phone.getServiceState().getState();
             switch (state) {
@@ -162,7 +162,7 @@
             CreateConnectionResponse<Connection> response) {
         Log.v(this, "onCreateIncomingConnection, request: " + request);
 
-        Phone phone = getPhoneForAccount(request.getAccountHandle());
+        Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
         if (phone == null) {
             response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
             return;
@@ -261,12 +261,20 @@
         return false;
     }
 
-    private Phone getPhoneForAccount(PhoneAccountHandle accountHandle) {
+    private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
+        if (isEmergency) {
+            return PhoneFactory.getDefaultPhone();
+        }
+
         if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) {
             if (accountHandle.getId() != null) {
-                int phoneId = SubscriptionController.getInstance().getPhoneId(
-                        Long.parseLong(accountHandle.getId()));
-                return PhoneFactory.getPhone(phoneId);
+                try {
+                    int phoneId = SubscriptionController.getInstance().getPhoneId(
+                            Long.parseLong(accountHandle.getId()));
+                    return PhoneFactory.getPhone(phoneId);
+                } catch (NumberFormatException e) {
+                    Log.w(this, "Could not get subId from account: " + accountHandle.getId());
+                }
             }
         }
         return null;
diff --git a/src/com/android/services/telephony/TelephonyGlobals.java b/src/com/android/services/telephony/TelephonyGlobals.java
index daeece6..70b287d 100644
--- a/src/com/android/services/telephony/TelephonyGlobals.java
+++ b/src/com/android/services/telephony/TelephonyGlobals.java
@@ -61,5 +61,7 @@
         if (phone != null) {
             mTtyManager = new TtyManager(mContext, phone);
         }
+
+        TelecommAccountRegistry.getInstance(mContext).setupOnBoot();
     }
 }