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