Squashed merge of master-sim into master.

Includes the following commits:

==

Let phone process bind to EuiccService implementation.

==

Implement EuiccManager actions through a dispatcher.

The dispatcher is a trampoline activity which forwards the given
action to the highest-priority LUI implementation after performing the
necessary prerequisite checks. It has priority=1000, which means no
APK other than those bundled with the system can supercede it.

==

Add eUICC UI into Mobile Network Setting Screen

Add a preference in Mobile Network Setting Screen to start the
eUICC settings. This preference will only be available when
EuiccManger#isEnabled is true.

==

Test: TreeHugger
Change-Id: I6463c31b577d66512151cc864b21c1ea028593f9
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bc1c448..85ad6bd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -144,6 +144,7 @@
     <uses-permission android:name="android.permission.BIND_CARRIER_SERVICES" />
     <!-- BIND_CARRIER_MESSAGING_SERVICE has been deprecated in favor of BIND_CARRIER_SERVICES. -->
     <uses-permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE" />
+    <uses-permission android:name="android.permission.BIND_EUICC_SERVICE" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
@@ -484,6 +485,19 @@
             </intent-filter>
         </service>
 
+        <!-- Handler for EuiccManager's public-facing intents. -->
+        <activity android:name=".EuiccUiDispatcherActivity"
+            android:theme="@android:style/Theme.NoDisplay">
+            <!-- Max out priority to ensure nobody else will handle these intents. -->
+            <intent-filter android:priority="1000">
+                <action android:name=
+                            "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS" />
+                <action android:name=
+                            "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTIONS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="EmergencyCallbackModeExitDialog"
             android:excludeFromRecents="true"
             android:label="@string/ecm_exit_dialog"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3108cfe..c9fd01e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -479,6 +479,10 @@
     <string name="roaming_warning">You may incur significant charges.</string>
     <!-- Mobile network settings screen, dialog message title when user selects the Data roaming check box -->
     <string name="roaming_alert_title">Allow data roaming?</string>
+    <!-- Mobile network settings screen, name of the option to manage carrier profiles on devices which support embedded carrier profiles -->
+    <string name="carrier_settings_euicc">Carrier</string>
+    <!-- Mobile network settings screen, summary of the option to manage carrier profiles on devices which support embedded carrier profiles -->
+    <string name="carrier_settings_euicc_summary"><xliff:g id="carrier_name">%1$s</xliff:g> &#8212; <xliff:g id="phone_number">%2$s</xliff:g></string>
 
     <!-- USSD aggregation dialog box: separator strings between messages (new-lines will be added before and after) -->
     <string name="ussd_dialog_sep" translatable="false">----------</string>
diff --git a/res/xml/network_setting_fragment.xml b/res/xml/network_setting_fragment.xml
index 875e1f3..0ea42bd 100644
--- a/res/xml/network_setting_fragment.xml
+++ b/res/xml/network_setting_fragment.xml
@@ -50,4 +50,8 @@
         android:persistent="false"
         android:summary="@string/enhanced_4g_lte_mode_summary"/>
 
+    <PreferenceScreen
+        android:key="carrier_settings_euicc_key"
+        android:title="@string/carrier_settings_euicc" />
+
 </PreferenceScreen>
diff --git a/src/com/android/phone/EuiccUiDispatcherActivity.java b/src/com/android/phone/EuiccUiDispatcherActivity.java
new file mode 100644
index 0000000..eaa3885
--- /dev/null
+++ b/src/com/android/phone/EuiccUiDispatcherActivity.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.phone;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.service.euicc.EuiccService;
+import android.telephony.euicc.EuiccManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.euicc.EuiccConnector;
+
+/** Trampoline activity to forward eUICC intents from apps to the active UI implementation. */
+public class EuiccUiDispatcherActivity extends Activity {
+    private static final String TAG = "EuiccUiDispatcher";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            Intent euiccUiIntent = getEuiccUiIntent();
+            if (euiccUiIntent != null) {
+                euiccUiIntent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+                startActivity(euiccUiIntent);
+            } else {
+                setResult(RESULT_CANCELED);
+            }
+        } finally {
+            // Since we're using Theme.NO_DISPLAY, we must always finish() at the end of onCreate().
+            finish();
+        }
+    }
+
+    /**
+     * Return a resolved Intent to start the Euicc app's UI for the given intent, or null if the
+     * implementation couldn't be resolved.
+     */
+    @VisibleForTesting
+    @Nullable
+    Intent getEuiccUiIntent() {
+        String action = getIntent().getAction();
+
+        EuiccManager euiccManager = (EuiccManager) getSystemService(Context.EUICC_SERVICE);
+        if (!euiccManager.isEnabled()) {
+            Log.w(TAG, "eUICC is not enabled; cannot start activity for action: " + action);
+            return null;
+        }
+
+        final String euiccUiAction;
+        switch (action) {
+            case EuiccManager.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS:
+                euiccUiAction = EuiccService.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS;
+                break;
+            case EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION:
+                if (isDeviceProvisioned()) {
+                    Log.w(TAG, "Cannot perform eUICC provisioning once device is provisioned");
+                    return null;
+                }
+                euiccUiAction = EuiccService.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION;
+                break;
+            default:
+                Log.w(TAG, "Unsupported action: " + action);
+                return null;
+        }
+
+        Intent euiccUiIntent = new Intent(euiccUiAction);
+        ActivityInfo activityInfo = findBestActivity(euiccUiIntent);
+
+        if (activityInfo == null) {
+            Log.w(TAG, "Could not resolve activity for action: " + euiccUiAction);
+            return null;
+        }
+        euiccUiIntent.setComponent(activityInfo.getComponentName());
+        return euiccUiIntent;
+    }
+
+    @VisibleForTesting
+    boolean isDeviceProvisioned() {
+        return Settings.Global.getInt(getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    ActivityInfo findBestActivity(Intent euiccUiIntent) {
+        return EuiccConnector.findBestActivity(getPackageManager(), euiccUiIntent);
+    }
+}
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index 8d58107..7125c79 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.phone;
 
+import android.annotation.Nullable;
 import android.app.ActionBar;
 import android.app.DialogFragment;
 import android.app.Fragment;
@@ -41,10 +42,12 @@
 import android.preference.PreferenceScreen;
 import android.preference.SwitchPreference;
 import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.MenuItem;
@@ -52,6 +55,7 @@
 import android.widget.TabHost;
 
 import com.android.ims.ImsManager;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
@@ -106,6 +110,9 @@
         // Number of active Subscriptions to show tabs
         private static final int TAB_THRESHOLD = 2;
 
+        // Number of last phone number digits shown in Euicc Setting tab
+        private static final int NUM_LAST_PHONE_DIGITS = 4;
+
         // fragment tag for roaming data dialog
         private static final String ROAMING_TAG = "RoamingDialogFragment";
 
@@ -120,6 +127,8 @@
         private static final String BUTTON_OPERATOR_SELECTION_EXPAND_KEY = "button_carrier_sel_key";
         private static final String BUTTON_CARRIER_SETTINGS_KEY = "carrier_settings_key";
         private static final String BUTTON_CDMA_SYSTEM_SELECT_KEY = "cdma_system_select_key";
+        private static final String BUTTON_CARRIER_SETTINGS_EUICC_KEY =
+                "carrier_settings_euicc_key";
 
         private final BroadcastReceiver mPhoneChangeReceiver = new PhoneChangeReceiver();
 
@@ -138,6 +147,7 @@
         private RestrictedSwitchPreference mButtonDataRoam;
         private SwitchPreference mButton4glte;
         private Preference mLteDataServicePref;
+        private Preference mEuiccSettingsPref;
 
         private static final String iface = "rmnet0"; //TODO: this will go away
         private List<SubscriptionInfo> mActiveSubInfos;
@@ -249,6 +259,10 @@
             } else if (preference == mButtonDataRoam) {
                 // Do not disable the preference screen if the user clicks Data roaming.
                 return true;
+            } else if (preference == mEuiccSettingsPref) {
+                Intent intent = new Intent(EuiccManager.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS);
+                startActivity(intent);
+                return true;
             } else {
                 // if the button is anything but the simple toggle preference,
                 // we'll need to disable all preferences to reject all click
@@ -464,6 +478,9 @@
 
             mLteDataServicePref = prefSet.findPreference(BUTTON_CDMA_LTE_DATA_SERVICE_KEY);
 
+            mEuiccSettingsPref = prefSet.findPreference(BUTTON_CARRIER_SETTINGS_EUICC_KEY);
+            mEuiccSettingsPref.setOnPreferenceChangeListener(this);
+
             // Initialize mActiveSubInfo
             int max = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
             mActiveSubInfos = new ArrayList<SubscriptionInfo>(max);
@@ -556,6 +573,18 @@
                 prefSet.addPreference(mButtonPreferredNetworkMode);
                 prefSet.addPreference(mButtonEnabledNetworks);
                 prefSet.addPreference(mButton4glte);
+                EuiccManager euiccManager =
+                        (EuiccManager) getActivity().getSystemService(Context.EUICC_SERVICE);
+                if (euiccManager.isEnabled()) {
+                    prefSet.addPreference(mEuiccSettingsPref);
+                    TelephonyManager tm =
+                        (TelephonyManager) getActivity()
+                                .getSystemService(Context.TELEPHONY_SERVICE);
+                    mEuiccSettingsPref.setSummary(
+                            getEuiccSettingsSummary(
+                                    tm.getSimOperatorName(),
+                                    PhoneNumberUtils.formatNumber(tm.getLine1Number())));
+                }
             }
 
             int settingsNetworkMode = android.provider.Settings.Global.getInt(
@@ -1037,6 +1066,30 @@
             mButtonPreferredNetworkMode.setValue(Integer.toString(settingsNetworkMode));
         }
 
+        @VisibleForTesting
+        String getEuiccSettingsSummary(@Nullable String spn, @Nullable String phoneNum) {
+            if (spn != null && phoneNum.length() >= NUM_LAST_PHONE_DIGITS) {
+                // Format the number and use the last one part or multiple
+                // parts whose total length is greater or equal to NUM_LAST_PHONE_DIGITS.
+                // TODO (b/36647649): This needs to be finalized by UX team
+                String shownNum;
+                int lastIndex = phoneNum.lastIndexOf('-');
+                if (lastIndex == -1) {
+                    shownNum = phoneNum.substring(phoneNum.length() - NUM_LAST_PHONE_DIGITS);
+                } else {
+                    shownNum = phoneNum.substring(lastIndex + 1);
+                    while (shownNum.length() < NUM_LAST_PHONE_DIGITS && lastIndex != -1) {
+                        lastIndex = phoneNum.lastIndexOf('-', lastIndex - 1);
+                        shownNum = phoneNum.substring(lastIndex + 1);
+                    }
+                }
+                return getString(R.string.carrier_settings_euicc_summary, spn, shownNum);
+            } else {
+                return null;
+            }
+        }
+
+
         private void UpdatePreferredNetworkModeSummary(int NetworkMode) {
             switch(NetworkMode) {
                 case Phone.NT_MODE_TDSCDMA_GSM_WCDMA:
diff --git a/tests/src/com/android/phone/EuiccUiDispatcherActivityTest.java b/tests/src/com/android/phone/EuiccUiDispatcherActivityTest.java
new file mode 100644
index 0000000..3385dc3
--- /dev/null
+++ b/tests/src/com/android/phone/EuiccUiDispatcherActivityTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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.phone;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.euicc.EuiccManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class EuiccUiDispatcherActivityTest {
+    private static final Intent MANAGE_INTENT =
+            new Intent(EuiccManager.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS);
+    private static final Intent PROVISION_INTENT =
+            new Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION);
+
+    private static final ActivityInfo ACTIVITY_INFO = new ActivityInfo();
+    static {
+        ACTIVITY_INFO.packageName = "test.package";
+        ACTIVITY_INFO.name = "TestClass";
+    }
+
+    @Mock private Context mMockContext;
+    @Mock private EuiccManager mMockEuiccManager;
+    private boolean mIsProvisioned = true;
+    private ActivityInfo mActivityInfo = ACTIVITY_INFO;
+    private Intent mIntent = MANAGE_INTENT;
+    private EuiccUiDispatcherActivity mActivity;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockEuiccManager.isEnabled()).thenReturn(true);
+        when(mMockContext.getSystemService(Context.EUICC_SERVICE)).thenReturn(mMockEuiccManager);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mActivity = new TestEuiccUiDispatcherActivity();
+                    }
+                }
+        );
+    }
+
+    @Test
+    public void testGetEuiccUiIntent_disabled() {
+        when(mMockEuiccManager.isEnabled()).thenReturn(false);
+        assertNull(mActivity.getEuiccUiIntent());
+    }
+
+    @Test
+    public void testGetEuiccIntent_unsupportedAction() {
+        mIntent = new Intent("fake.action");
+        assertNull(mActivity.getEuiccUiIntent());
+    }
+
+    @Test
+    public void testGetEuiccIntent_alreadyProvisioned() {
+        mIntent = PROVISION_INTENT;
+        assertNull(mActivity.getEuiccUiIntent());
+    }
+
+    @Test
+    public void testGetEuiccIntent_noImplementation() {
+        mActivityInfo = null;
+        assertNull(mActivity.getEuiccUiIntent());
+    }
+
+    @Test
+    public void testGetEuiccIntent_validManage() {
+        assertNotNull(mActivity.getEuiccUiIntent());
+    }
+
+    @Test
+    public void testGetEuiccIntent_validProvision() {
+        mIsProvisioned = false;
+        assertNotNull(mActivity.getEuiccUiIntent());
+    }
+
+    class TestEuiccUiDispatcherActivity extends EuiccUiDispatcherActivity {
+        public TestEuiccUiDispatcherActivity() {
+            attachBaseContext(mMockContext);
+        }
+
+        @Override
+        public Intent getIntent() {
+            return mIntent;
+        }
+
+        @Override
+        boolean isDeviceProvisioned() {
+            return mIsProvisioned;
+        }
+
+        @Override
+        ActivityInfo findBestActivity(Intent euiccUiIntent) {
+            return mActivityInfo;
+        }
+    }
+}
diff --git a/tests/src/com/android/phone/MobileNetworkSettingsTest.java b/tests/src/com/android/phone/MobileNetworkSettingsTest.java
new file mode 100644
index 0000000..0c9e69a
--- /dev/null
+++ b/tests/src/com/android/phone/MobileNetworkSettingsTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.phone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.PhoneNumberUtils;
+
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+public class MobileNetworkSettingsTest {
+
+    @Rule
+    public ActivityTestRule<MobileNetworkSettings> mRule =
+            new ActivityTestRule<>(MobileNetworkSettings.class);
+
+    private Activity mActivity;
+    private MobileNetworkSettings.MobileNetworkFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mActivity = mRule.getActivity();
+        FragmentManager fragmentManager = mActivity.getFragmentManager();
+        mFragment = (MobileNetworkSettings.MobileNetworkFragment)
+                fragmentManager.findFragmentById(R.id.network_setting_content);
+    }
+
+    @Test
+    public void testGetEuiccSettingsSummary() {
+
+        assertNull(mFragment.getEuiccSettingsSummary(null, "1234"));
+        assertNull(mFragment.getEuiccSettingsSummary("spn", "123"));
+        assertEquals(mFragment.getEuiccSettingsSummary("spn", "123456789"),
+                mFragment.getString(R.string.carrier_settings_euicc_summary, "spn", "6789"));
+        assertEquals(mFragment.getEuiccSettingsSummary("spn", "1234-56-78"),
+                mFragment.getString(R.string.carrier_settings_euicc_summary, "spn", "56-78"));
+        assertEquals(mFragment.getEuiccSettingsSummary("spn", "1234-56789"),
+                mFragment.getString(R.string.carrier_settings_euicc_summary, "spn", "56789"));
+        assertEquals(mFragment.getEuiccSettingsSummary("spn", "56-789"),
+                mFragment.getString(R.string.carrier_settings_euicc_summary, "spn", "56-789"));
+        assertEquals(
+                mFragment.getEuiccSettingsSummary(
+                    "spn", PhoneNumberUtils.formatNumber("16501234567")),
+                mFragment.getString(R.string.carrier_settings_euicc_summary, "spn", "4567"));
+    }
+}