Merge "Implement dispatching of resolution UI."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2f031fa..85e2982 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -487,7 +487,7 @@
         </service>
 
         <!-- Handler for EuiccManager's public-facing intents. -->
-        <activity android:name=".EuiccUiDispatcherActivity"
+        <activity android:name=".euicc.EuiccUiDispatcherActivity"
             android:theme="@android:style/Theme.NoDisplay">
             <!-- Max out priority to ensure nobody else will handle these intents. -->
             <intent-filter android:priority="1000">
@@ -499,6 +499,21 @@
             </intent-filter>
         </activity>
 
+        <!--
+            Handler for EuiccManager's resolution intents. These are locked down so that only
+            privileged processes can start them, which means we can trust the Intent used to start
+            it (which contains a description of the next step to perform after resolution).
+        -->
+        <activity android:name=".euicc.EuiccResolutionUiDispatcherActivity"
+            android:permission="android.permission.CALL_PRIVILEGED">
+            <!-- Max out priority to ensure nobody else will handle these intents. -->
+            <intent-filter android:priority="1000">
+                <action android:name=
+                            "android.telephony.euicc.action.RESOLVE_ERROR" />
+                <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/src/com/android/phone/euicc/EuiccResolutionUiDispatcherActivity.java b/src/com/android/phone/euicc/EuiccResolutionUiDispatcherActivity.java
new file mode 100644
index 0000000..6f28bbf
--- /dev/null
+++ b/src/com/android/phone/euicc/EuiccResolutionUiDispatcherActivity.java
@@ -0,0 +1,57 @@
+/*
+ * 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.euicc;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.service.euicc.EuiccService;
+import android.telephony.euicc.EuiccManager;
+import android.util.Log;
+
+/**
+ * Trampoline activity to forward eUICC intents for error resolutions to the active UI
+ * implementation.
+ *
+ * <p>Unlike {@link EuiccUiDispatcherActivity}, this activity is started with extras that must not
+ * be tampered with, because they are used to resume the operation after the error is resolved. We
+ * thus declare it as a separate activity which requires a locked-down permission to start.
+ */
+public class EuiccResolutionUiDispatcherActivity extends EuiccUiDispatcherActivity {
+    private static final String TAG = "EuiccResUiDispatcher";
+
+    @Override
+    @Nullable
+    protected Intent getEuiccUiIntent() {
+        String action = getIntent().getAction();
+        if (!EuiccManager.ACTION_RESOLVE_ERROR.equals(action)) {
+            Log.w(TAG, "Unsupported action: " + action);
+            return null;
+        }
+
+        String euiccUiAction =
+                getIntent().getStringExtra(
+                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION);
+        if (!EuiccService.RESOLUTION_ACTIONS.contains(euiccUiAction)) {
+            Log.w(TAG, "Unknown resolution action: " + euiccUiAction);
+            return null;
+        }
+
+        Intent euiccUiIntent = new Intent(euiccUiAction);
+        // Propagate the extras from the original Intent.
+        euiccUiIntent.putExtras(getIntent());
+        return euiccUiIntent;
+    }
+}
diff --git a/src/com/android/phone/EuiccUiDispatcherActivity.java b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
similarity index 78%
rename from src/com/android/phone/EuiccUiDispatcherActivity.java
rename to src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
index eaa3885..d96befa 100644
--- a/src/com/android/phone/EuiccUiDispatcherActivity.java
+++ b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.phone;
+package com.android.phone.euicc;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -37,35 +37,54 @@
     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 {
+            Intent euiccUiIntent = resolveEuiccUiIntent();
+            if (euiccUiIntent == null) {
                 setResult(RESULT_CANCELED);
+                return;
             }
+
+            euiccUiIntent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+            startActivity(euiccUiIntent);
         } 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();
-
+    Intent resolveEuiccUiIntent() {
         EuiccManager euiccManager = (EuiccManager) getSystemService(Context.EUICC_SERVICE);
         if (!euiccManager.isEnabled()) {
-            Log.w(TAG, "eUICC is not enabled; cannot start activity for action: " + action);
+            setResult(RESULT_CANCELED);
             return null;
         }
 
-        final String euiccUiAction;
+        Intent euiccUiIntent = getEuiccUiIntent();
+        if (euiccUiIntent == null) {
+            Log.w(TAG, "Unable to handle intent");
+            return null;
+        }
+
+        ActivityInfo activityInfo = findBestActivity(euiccUiIntent);
+        if (activityInfo == null) {
+            Log.w(TAG, "Could not resolve activity for intent: " + euiccUiIntent);
+            return null;
+        }
+
+        euiccUiIntent.setComponent(activityInfo.getComponentName());
+        return euiccUiIntent;
+    }
+
+    /**
+     * Return an Intent to start the Euicc app's UI for the given intent, or null if given intent
+     * cannot be handled.
+     */
+    @Nullable
+    protected Intent getEuiccUiIntent() {
+        String action = getIntent().getAction();
+
+        String euiccUiAction;
         switch (action) {
             case EuiccManager.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS:
                 euiccUiAction = EuiccService.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS;
@@ -82,15 +101,7 @@
                 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;
+        return new Intent(euiccUiAction);
     }
 
     @VisibleForTesting
diff --git a/tests/src/com/android/phone/EuiccUiDispatcherActivityTest.java b/tests/src/com/android/phone/euicc/EuiccUiDispatcherActivityTest.java
similarity index 81%
rename from tests/src/com/android/phone/EuiccUiDispatcherActivityTest.java
rename to tests/src/com/android/phone/euicc/EuiccUiDispatcherActivityTest.java
index 3385dc3..722e1bd 100644
--- a/tests/src/com/android/phone/EuiccUiDispatcherActivityTest.java
+++ b/tests/src/com/android/phone/euicc/EuiccUiDispatcherActivityTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.phone;
+package com.android.phone.euicc;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -68,38 +68,38 @@
     }
 
     @Test
-    public void testGetEuiccUiIntent_disabled() {
+    public void testResolveEuiccUiIntent_disabled() {
         when(mMockEuiccManager.isEnabled()).thenReturn(false);
-        assertNull(mActivity.getEuiccUiIntent());
+        assertNull(mActivity.resolveEuiccUiIntent());
     }
 
     @Test
-    public void testGetEuiccIntent_unsupportedAction() {
+    public void testResolveEuiccUiIntent_unsupportedAction() {
         mIntent = new Intent("fake.action");
-        assertNull(mActivity.getEuiccUiIntent());
+        assertNull(mActivity.resolveEuiccUiIntent());
     }
 
     @Test
-    public void testGetEuiccIntent_alreadyProvisioned() {
+    public void testResolveEuiccUiIntent_alreadyProvisioned() {
         mIntent = PROVISION_INTENT;
-        assertNull(mActivity.getEuiccUiIntent());
+        assertNull(mActivity.resolveEuiccUiIntent());
     }
 
     @Test
-    public void testGetEuiccIntent_noImplementation() {
+    public void testResolveEuiccUiIntent_noImplementation() {
         mActivityInfo = null;
-        assertNull(mActivity.getEuiccUiIntent());
+        assertNull(mActivity.resolveEuiccUiIntent());
     }
 
     @Test
-    public void testGetEuiccIntent_validManage() {
-        assertNotNull(mActivity.getEuiccUiIntent());
+    public void testResolveEuiccUiIntent_validManage() {
+        assertNotNull(mActivity.resolveEuiccUiIntent());
     }
 
     @Test
-    public void testGetEuiccIntent_validProvision() {
+    public void testResolveEuiccUiIntent_validProvision() {
         mIsProvisioned = false;
-        assertNotNull(mActivity.getEuiccUiIntent());
+        assertNotNull(mActivity.resolveEuiccUiIntent());
     }
 
     class TestEuiccUiDispatcherActivity extends EuiccUiDispatcherActivity {