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