• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2022 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 
17 package com.android.settings.network.telephony;
18 
19 import static androidx.lifecycle.Lifecycle.Event.ON_START;
20 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
21 
22 import android.Manifest;
23 import android.app.Activity;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.ComponentInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.ServiceInfo;
32 import android.service.euicc.EuiccService;
33 import android.telephony.SubscriptionInfo;
34 import android.telephony.SubscriptionManager;
35 import android.telephony.euicc.EuiccManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import androidx.annotation.VisibleForTesting;
40 import androidx.lifecycle.LifecycleObserver;
41 import androidx.lifecycle.LifecycleOwner;
42 import androidx.lifecycle.OnLifecycleEvent;
43 import androidx.preference.Preference;
44 import androidx.preference.PreferenceScreen;
45 
46 import com.android.internal.telephony.flags.Flags;
47 import com.android.internal.telephony.util.TelephonyUtils;
48 import com.android.settings.network.MobileNetworkRepository;
49 import com.android.settingslib.core.lifecycle.Lifecycle;
50 import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
51 
52 import org.jetbrains.annotations.NotNull;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 
57 public class ConvertToEsimPreferenceController extends TelephonyBasePreferenceController implements
58         LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback {
59     private static final String TAG = "ConvertToEsimPreference";
60     private Preference mPreference;
61     private LifecycleOwner mLifecycleOwner;
62     private MobileNetworkRepository mMobileNetworkRepository;
63     private List<SubscriptionInfoEntity> mSubscriptionInfoEntityList = new ArrayList<>();
64     private SubscriptionInfoEntity mSubscriptionInfoEntity;
65     private static int sQueryFlag =
66             PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DIRECT_BOOT_AUTO
67                     | PackageManager.GET_RESOLVED_FILTER;
68 
ConvertToEsimPreferenceController(Context context, String key, Lifecycle lifecycle, LifecycleOwner lifecycleOwner, int subId)69     public ConvertToEsimPreferenceController(Context context, String key, Lifecycle lifecycle,
70             LifecycleOwner lifecycleOwner, int subId) {
71         this(context, key);
72         mSubId = subId;
73         mLifecycleOwner = lifecycleOwner;
74         if (lifecycle != null) {
75             lifecycle.addObserver(this);
76         }
77     }
78 
ConvertToEsimPreferenceController(Context context, String key)79     public ConvertToEsimPreferenceController(Context context, String key) {
80         super(context, key);
81         mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
82     }
83 
init(int subId, SubscriptionInfoEntity subInfoEntity)84     public void init(int subId, SubscriptionInfoEntity subInfoEntity) {
85         mSubId = subId;
86         mSubscriptionInfoEntity = subInfoEntity;
87     }
88 
89     @OnLifecycleEvent(ON_START)
onStart()90     public void onStart() {
91         mMobileNetworkRepository.addRegister(mLifecycleOwner, this, mSubId);
92         mMobileNetworkRepository.updateEntity();
93     }
94 
95     @OnLifecycleEvent(ON_STOP)
onStop()96     public void onStop() {
97         mMobileNetworkRepository.removeRegister(this);
98     }
99 
100     @Override
displayPreference(PreferenceScreen screen)101     public void displayPreference(PreferenceScreen screen) {
102         super.displayPreference(screen);
103         mPreference = screen.findPreference(getPreferenceKey());
104     }
105 
106     @Override
getAvailabilityStatus(int subId)107     public int getAvailabilityStatus(int subId) {
108         // TODO(b/315073761) : Add a new API to set whether the profile has been
109         // converted/transferred. Remove any confusion to the user according to the set value.
110 
111         /*
112          * If pSIM is set to preferred SIM and there is an active eSIM, convert the pSIM to eSIM
113          * and then disable the pSIM.
114          * This causes a dialog to switch the preferred SIM to downloaded new eSIM.
115          * This may cause confusion for the user about the seamless conversion.
116          * To avoid showing users dialogs that can cause confusion,
117          * add conditions to allow conversion in the absence of active eSIM.
118          */
119         if (!Flags.supportPsimToEsimConversion()) {
120             Log.d(TAG, "supportPsimToEsimConversion flag is not enabled");
121             return CONDITIONALLY_UNAVAILABLE;
122         }
123 
124         SubscriptionManager subscriptionManager = mContext.getSystemService(
125                 SubscriptionManager.class);
126         SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfo(subId);
127         if (subInfo == null) {
128             return CONDITIONALLY_UNAVAILABLE;
129         }
130         EuiccManager euiccManager = (EuiccManager)
131                 mContext.getSystemService(Context.EUICC_SERVICE);
132         try {
133             if (!euiccManager.isPsimConversionSupported(subInfo.getCarrierId())) {
134                 Log.i(TAG, "subId is not matched with pSIM conversion"
135                         + " supported carriers:" + subInfo.getCarrierId());
136                 return CONDITIONALLY_UNAVAILABLE;
137             }
138             if (findConversionSupportComponent()) {
139                 return mSubscriptionInfoEntity != null
140                         && mSubscriptionInfoEntity.isActiveSubscriptionId
141                         && !mSubscriptionInfoEntity.isEmbedded && isActiveSubscription(subId)
142                         ? AVAILABLE
143                         : CONDITIONALLY_UNAVAILABLE;
144             }
145         } catch (RuntimeException e) {
146             Log.e(TAG, "Fail to check pSIM conversion supported carrier: " + e.getMessage());
147         }
148         return CONDITIONALLY_UNAVAILABLE;
149     }
150 
151     @VisibleForTesting
update()152     void update() {
153         if (mPreference == null) {
154             return;
155         }
156         mPreference.setVisible(getAvailabilityStatus(mSubId) == AVAILABLE);
157     }
158 
159     @Override
handlePreferenceTreeClick(Preference preference)160     public boolean handlePreferenceTreeClick(Preference preference) {
161         if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
162             return false;
163         }
164         // Send intent to launch LPA
165         Intent intent = new Intent(EuiccManager.ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION);
166         intent.putExtra("subId", mSubId);
167         mContext.startActivity(intent);
168         ((Activity) mContext).finish();
169         return true;
170     }
171 
172     @VisibleForTesting
setSubscriptionInfoEntity(SubscriptionInfoEntity subscriptionInfoEntity)173     public void setSubscriptionInfoEntity(SubscriptionInfoEntity subscriptionInfoEntity) {
174         mSubscriptionInfoEntity = subscriptionInfoEntity;
175     }
176 
177     @Override
onActiveSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList)178     public void onActiveSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
179         mSubscriptionInfoEntityList = subInfoEntityList;
180         mSubscriptionInfoEntityList.forEach(entity -> {
181             if (Integer.parseInt(entity.subId) == mSubId) {
182                 mSubscriptionInfoEntity = entity;
183                 update();
184             }
185         });
186     }
187 
isActiveSubscription(int subId)188     private boolean isActiveSubscription(int subId) {
189         SubscriptionManager subscriptionManager = mContext.getSystemService(
190                 SubscriptionManager.class);
191         SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfo(subId);
192         if (subInfo == null) {
193             return false;
194         }
195         return true;
196     }
197 
findConversionSupportComponent()198     private boolean findConversionSupportComponent() {
199         Intent intent = new Intent(EuiccService.ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION);
200         PackageManager packageManager = mContext.getPackageManager();
201         List<ResolveInfo> resolveInfoList = packageManager
202                 .queryIntentActivities(intent, sQueryFlag);
203         if (resolveInfoList == null || resolveInfoList.isEmpty()) {
204             return false;
205         }
206         for (ResolveInfo resolveInfo : resolveInfoList) {
207             if (!isValidEuiccComponent(packageManager, resolveInfo)) {
208                 continue;
209             } else {
210                 return true;
211             }
212         }
213         return true;
214     }
215 
isValidEuiccComponent( PackageManager packageManager, @NotNull ResolveInfo resolveInfo)216     private boolean isValidEuiccComponent(
217             PackageManager packageManager, @NotNull ResolveInfo resolveInfo) {
218         ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(resolveInfo);
219         String packageName = new ComponentName(componentInfo.packageName, componentInfo.name)
220                 .getPackageName();
221 
222         // Verify that the app is privileged (via granting of a privileged permission).
223         if (packageManager.checkPermission(
224                 Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS, packageName)
225                 != PackageManager.PERMISSION_GRANTED) {
226             return false;
227         }
228 
229         // Verify that only the system can access the component.
230         final String permission;
231         if (componentInfo instanceof ServiceInfo) {
232             permission = ((ServiceInfo) componentInfo).permission;
233         } else if (componentInfo instanceof ActivityInfo) {
234             permission = ((ActivityInfo) componentInfo).permission;
235         } else {
236             return false;
237         }
238         if (!TextUtils.equals(permission, Manifest.permission.BIND_EUICC_SERVICE)) {
239             return false;
240         }
241 
242         // Verify that the component declares a priority.
243         if (resolveInfo.filter == null || resolveInfo.filter.getPriority() == 0) {
244             return false;
245         }
246         return true;
247     }
248 }
249