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> — <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"));
+ }
+}