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