1 /* 2 * Copyright (C) 2017 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 package com.android.phone.euicc; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.app.Activity; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.ActivityInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 import android.permission.LegacyPermissionManager; 30 import android.service.euicc.EuiccService; 31 import android.telephony.euicc.EuiccManager; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.internal.telephony.euicc.EuiccConnector; 37 import com.android.internal.telephony.util.TelephonyUtils; 38 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Set; 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.LinkedBlockingQueue; 44 import java.util.concurrent.ThreadFactory; 45 import java.util.concurrent.ThreadPoolExecutor; 46 import java.util.concurrent.TimeUnit; 47 import java.util.concurrent.atomic.AtomicInteger; 48 49 /** Trampoline activity to forward eUICC intents from apps to the active UI implementation. */ 50 public class EuiccUiDispatcherActivity extends Activity { 51 private static final String TAG = "EuiccUiDispatcher"; 52 private static final long CHANGE_PERMISSION_TIMEOUT_MS = 15 * 1000; // 15 seconds 53 54 /** Flags to use when querying PackageManager for Euicc component implementations. */ 55 private static final int EUICC_QUERY_FLAGS = 56 PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING 57 | PackageManager.GET_RESOLVED_FILTER; 58 59 private LegacyPermissionManager mPermissionManager; 60 private boolean mGrantPermissionDone = false; 61 private ThreadPoolExecutor mExecutor; 62 // Used for CTS EuiccManager action verification 63 private static ComponentName mTestEuiccUiComponentName; 64 65 @Override onCreate(Bundle savedInstanceState)66 public void onCreate(Bundle savedInstanceState) { 67 super.onCreate(savedInstanceState); 68 mPermissionManager = (LegacyPermissionManager) getSystemService( 69 Context.LEGACY_PERMISSION_SERVICE); 70 mExecutor = new ThreadPoolExecutor( 71 1 /* corePoolSize */, 72 1 /* maxPoolSize */, 73 15, TimeUnit.SECONDS, /* keepAliveTime */ 74 new LinkedBlockingQueue<>(), /* workQueue */ 75 new ThreadFactory() { 76 private final AtomicInteger mCount = new AtomicInteger(1); 77 78 @Override 79 public Thread newThread(Runnable r) { 80 return new Thread(r, "EuiccService #" + mCount.getAndIncrement()); 81 } 82 } 83 ); 84 try { 85 Intent euiccUiIntent = resolveEuiccUiIntent(); 86 if (euiccUiIntent == null) { 87 setResult(RESULT_CANCELED); 88 onDispatchFailure(); 89 return; 90 } 91 92 euiccUiIntent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 93 startActivity(euiccUiIntent); 94 } finally { 95 // Since we're using Theme.NO_DISPLAY, we must always finish() at the end of onCreate(). 96 finish(); 97 } 98 } 99 100 /** 101 * This API used to set the Test EuiccUiComponent for CTS 102 * @param packageName package which handles the intent 103 * @param componentName ui component to be launched for testing 104 */ setTestEuiccUiComponent(String packageName, String componentName)105 public static void setTestEuiccUiComponent(String packageName, String componentName) { 106 mTestEuiccUiComponentName = null; 107 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(componentName)) { 108 mTestEuiccUiComponentName = new ComponentName(packageName, componentName); 109 } 110 } 111 112 @VisibleForTesting 113 @Nullable resolveEuiccUiIntent()114 Intent resolveEuiccUiIntent() { 115 EuiccManager euiccManager = (EuiccManager) getSystemService(Context.EUICC_SERVICE); 116 if (euiccManager == null || !euiccManager.isEnabled()) { 117 Log.w(TAG, "eUICC not enabled"); 118 return null; 119 } 120 121 Intent euiccUiIntent = getEuiccUiIntent(); 122 if (euiccUiIntent == null) { 123 Log.w(TAG, "Unable to handle intent"); 124 return null; 125 } 126 127 if (mTestEuiccUiComponentName != null) { 128 Log.i(TAG, "Test mode"); 129 euiccUiIntent.setComponent(mTestEuiccUiComponentName); 130 mTestEuiccUiComponentName = null; 131 return euiccUiIntent; 132 } 133 134 revokePermissionFromLuiApps(euiccUiIntent); 135 136 ActivityInfo activityInfo = findBestActivity(euiccUiIntent); 137 if (activityInfo == null) { 138 Log.w(TAG, "Could not resolve activity for intent: " + euiccUiIntent); 139 return null; 140 } 141 142 grantDefaultPermissionsToLuiApp(activityInfo); 143 144 euiccUiIntent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name)); 145 return euiccUiIntent; 146 } 147 148 /** Called when dispatch fails. May be overridden to perform some operation here. */ onDispatchFailure()149 protected void onDispatchFailure() { 150 } 151 152 /** 153 * Return an Intent to start the Euicc app's UI for the given intent, or null if given intent 154 * cannot be handled. 155 */ 156 @Nullable getEuiccUiIntent()157 protected Intent getEuiccUiIntent() { 158 String action = getIntent().getAction(); 159 if (action == null) { 160 Log.w(TAG, "No action is specified in the intent"); 161 return null; 162 } 163 164 Intent intent = new Intent(); 165 intent.putExtras(getIntent()); 166 switch (action) { 167 case EuiccManager.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS: 168 intent.setAction(EuiccService.ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS); 169 break; 170 case EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION: 171 intent.setAction(EuiccService.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION); 172 break; 173 case EuiccManager.ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS: 174 intent.setAction(EuiccService.ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS); 175 break; 176 case EuiccManager.ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION: 177 intent.setAction(EuiccService.ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION); 178 break; 179 default: 180 Log.w(TAG, "Unsupported action: " + action); 181 return null; 182 } 183 184 return intent; 185 } 186 187 @Override onDestroy()188 protected void onDestroy() { 189 mExecutor.shutdownNow(); 190 super.onDestroy(); 191 } 192 193 @VisibleForTesting 194 @Nullable findBestActivity(Intent euiccUiIntent)195 ActivityInfo findBestActivity(Intent euiccUiIntent) { 196 return EuiccConnector.findBestActivity(getPackageManager(), euiccUiIntent); 197 } 198 199 /** Grants default permissions to the active LUI app. */ 200 @VisibleForTesting grantDefaultPermissionsToLuiApp(ActivityInfo activityInfo)201 protected void grantDefaultPermissionsToLuiApp(ActivityInfo activityInfo) { 202 CountDownLatch latch = new CountDownLatch(1); 203 try { 204 mPermissionManager.grantDefaultPermissionsToLuiApp( 205 activityInfo.packageName, UserHandle.of(UserHandle.myUserId()), mExecutor, 206 isSuccess -> { 207 if (isSuccess) { 208 latch.countDown(); 209 } else { 210 Log.e(TAG, "Failed to revoke LUI app permissions."); 211 } 212 }); 213 TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS); 214 } catch (RuntimeException e) { 215 Log.e(TAG, "Failed to grant permissions to active LUI app.", e); 216 } 217 } 218 219 /** Cleans up all the packages that shouldn't have permission. */ 220 @VisibleForTesting revokePermissionFromLuiApps(Intent intent)221 protected void revokePermissionFromLuiApps(Intent intent) { 222 CountDownLatch latch = new CountDownLatch(1); 223 try { 224 Set<String> luiApps = getAllLuiAppPackageNames(intent); 225 String[] luiAppsArray = luiApps.toArray(new String[luiApps.size()]); 226 mPermissionManager.revokeDefaultPermissionsFromLuiApps(luiAppsArray, 227 UserHandle.of(UserHandle.myUserId()), mExecutor, isSuccess -> { 228 if (isSuccess) { 229 latch.countDown(); 230 } else { 231 Log.e(TAG, "Failed to revoke LUI app permissions."); 232 } 233 }); 234 TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS); 235 } catch (RuntimeException e) { 236 Log.e(TAG, "Failed to revoke LUI app permissions."); 237 throw e; 238 } 239 } 240 241 @NonNull getAllLuiAppPackageNames(Intent intent)242 private Set<String> getAllLuiAppPackageNames(Intent intent) { 243 List<ResolveInfo> luiPackages = 244 getPackageManager().queryIntentServices(intent, EUICC_QUERY_FLAGS); 245 HashSet<String> packageNames = new HashSet<>(); 246 for (ResolveInfo info : luiPackages) { 247 if (info.serviceInfo == null) continue; 248 packageNames.add(info.serviceInfo.packageName); 249 } 250 return packageNames; 251 } 252 } 253