Skip to content

Commit e4d2666

Browse files
jscott1989pfmaggi
authored andcommitted
No public description
PiperOrigin-RevId: 629025089
1 parent 328636b commit e4d2666

File tree

7 files changed

+393
-1
lines changed

7 files changed

+393
-1
lines changed
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
/*
2+
* Copyright (C) 2024 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.afwsamples.testdpc.policy;
18+
19+
import android.annotation.TargetApi;
20+
import android.app.Activity;
21+
import android.app.AlertDialog;
22+
import android.app.PendingIntent;
23+
import android.app.admin.DevicePolicyManager;
24+
import android.content.BroadcastReceiver;
25+
import android.content.Context;
26+
import android.content.Intent;
27+
import android.content.IntentFilter;
28+
import android.os.Build.VERSION_CODES;
29+
import android.os.Bundle;
30+
import android.telephony.euicc.DownloadableSubscription;
31+
import android.telephony.euicc.EuiccManager;
32+
import android.util.Log;
33+
import android.view.View;
34+
import android.widget.CheckBox;
35+
import android.widget.EditText;
36+
import android.widget.Toast;
37+
import androidx.core.content.ContextCompat;
38+
import androidx.preference.Preference;
39+
import com.afwsamples.testdpc.R;
40+
import com.afwsamples.testdpc.common.BaseSearchablePolicyPreferenceFragment;
41+
import com.afwsamples.testdpc.common.ReflectionUtil;
42+
import com.afwsamples.testdpc.common.ReflectionUtil.ReflectionIsTemporaryException;
43+
import com.afwsamples.testdpc.common.preference.DpcPreference;
44+
import java.util.Set;
45+
46+
/** Fragment to control eSIMs. */
47+
@TargetApi(VERSION_CODES.VANILLA_ICE_CREAM)
48+
public class EsimControlFragment extends BaseSearchablePolicyPreferenceFragment
49+
implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
50+
private static final String TAG = EsimControlFragment.class.getSimpleName();
51+
private static final String DOWNLOADE_ESIM = "download_esim";
52+
private static final String DELETE_ESIM = "delete_esim";
53+
private static final String GET_MANAGED_ESIM = "get_managed_esim";
54+
private static final String ACTION_DOWNLOAD_ESIM = "com.afwsamples.testdpc.esim_download";
55+
private static final String ACTION_DELETE_ESIM = "com.afwsamples.testdpc.esim_delete";
56+
57+
private DpcPreference mDownloadEsimPreference;
58+
private DpcPreference mDeleteEsimPreference;
59+
private DpcPreference mGetManagedEsimPreference;
60+
private DevicePolicyManager mDevicePolicyManager;
61+
private EuiccManager mEuiccManager;
62+
63+
private String getResultText(int resultCode) {
64+
if (resultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
65+
return "EMBEDDED_SUBSCRIPTION_RESULT_OK";
66+
} else if (resultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR) {
67+
return "EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR";
68+
} else if (resultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR) {
69+
return "EMBEDDED_SUBSCRIPTION_RESULT_ERROR";
70+
}
71+
return "Uknown: " + resultCode;
72+
}
73+
74+
@Override
75+
public void onCreate(Bundle savedInstanceState) {
76+
mDevicePolicyManager = getActivity().getSystemService(DevicePolicyManager.class);
77+
mEuiccManager = getActivity().getSystemService(EuiccManager.class);
78+
getActivity().getActionBar().setTitle(R.string.manage_esim);
79+
super.onCreate(savedInstanceState);
80+
}
81+
82+
private BroadcastReceiver mDownloadESIMReceiver =
83+
new BroadcastReceiver() {
84+
@Override
85+
public void onReceive(Context context, Intent intent) {
86+
87+
if (!ACTION_DOWNLOAD_ESIM.equals(intent.getAction())) {
88+
return;
89+
}
90+
int detailedCode =
91+
intent.getIntExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, -1);
92+
int errorCode =
93+
intent.getIntExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE, -1);
94+
95+
Log.v(
96+
TAG,
97+
"Download result: resultCode: "
98+
+ getResultText(getResultCode())
99+
+ " detailedCode: "
100+
+ getResultCode()
101+
+ " detailedCode: "
102+
+ detailedCode
103+
+ " errorCode: "
104+
+ errorCode);
105+
showToast("Download result: " + getResultText(getResultCode()), Toast.LENGTH_LONG);
106+
getActivity().unregisterReceiver(mDownloadESIMReceiver);
107+
}
108+
};
109+
110+
private BroadcastReceiver mDeleteESIMReceiver =
111+
new BroadcastReceiver() {
112+
@Override
113+
public void onReceive(Context context, Intent intent) {
114+
if (!ACTION_DELETE_ESIM.equals(intent.getAction())) {
115+
return;
116+
}
117+
int detailedCode =
118+
intent.getIntExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, -1);
119+
Log.v(
120+
TAG,
121+
"Delete result: resultCode: "
122+
+ getResultText(getResultCode())
123+
+ " detailedCode: "
124+
+ detailedCode);
125+
126+
showToast("Delete result: " + getResultText(getResultCode()), Toast.LENGTH_LONG);
127+
getActivity().unregisterReceiver(mDeleteESIMReceiver);
128+
}
129+
};
130+
131+
@Override
132+
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
133+
addPreferencesFromResource(R.xml.esim_control_preferences);
134+
135+
mDownloadEsimPreference = (DpcPreference) findPreference(DOWNLOADE_ESIM);
136+
mDownloadEsimPreference.setOnPreferenceClickListener(this);
137+
138+
mDeleteEsimPreference = (DpcPreference) findPreference(DELETE_ESIM);
139+
mDeleteEsimPreference.setOnPreferenceClickListener(this);
140+
141+
mGetManagedEsimPreference = (DpcPreference) findPreference(GET_MANAGED_ESIM);
142+
mGetManagedEsimPreference.setOnPreferenceClickListener(this);
143+
}
144+
145+
@Override
146+
public boolean isAvailable(Context context) {
147+
return true;
148+
}
149+
150+
@Override
151+
public boolean onPreferenceChange(Preference preference, Object newValue) {
152+
return false;
153+
}
154+
155+
@Override
156+
public boolean onPreferenceClick(Preference preference) {
157+
String key = preference.getKey();
158+
159+
switch (key) {
160+
case DOWNLOADE_ESIM:
161+
showDownloadEsimUi();
162+
return true;
163+
case DELETE_ESIM:
164+
showDeleteEsimUi();
165+
return true;
166+
case GET_MANAGED_ESIM:
167+
showManagedEsimUi();
168+
return true;
169+
}
170+
return false;
171+
}
172+
173+
private void showManagedEsimUi() {
174+
Set<Integer> managedSubIds = getSubscriptionIds();
175+
new AlertDialog.Builder(getActivity())
176+
.setTitle(R.string.get_managed_esim_dialog_title)
177+
.setItems(managedSubIds.stream().map(String::valueOf).toArray(String[]::new), null)
178+
.show();
179+
}
180+
181+
private Set<Integer> getSubscriptionIds() {
182+
try {
183+
// TODO: remove reflection code and call directly once V is released.
184+
return ReflectionUtil.invoke(mDevicePolicyManager, "getSubscriptionIds");
185+
} catch (ReflectionIsTemporaryException e) {
186+
Log.e(TAG, "Error invoking getSubscriptionIds", e);
187+
showToast("Error getting managed esim information.", Toast.LENGTH_LONG);
188+
}
189+
return null;
190+
}
191+
192+
private void showToast(String msg, int duration) {
193+
Activity activity = getActivity();
194+
if (activity == null || activity.isFinishing()) {
195+
Log.w(TAG, "Not toasting '" + msg + "' as activity is finishing or finished");
196+
return;
197+
}
198+
Log.d(TAG, "Showing toast: " + msg);
199+
Toast.makeText(activity, msg, duration).show();
200+
}
201+
202+
private void showDownloadEsimUi() {
203+
if (getActivity() == null || getActivity().isFinishing()) {
204+
return;
205+
}
206+
207+
final View dialogView =
208+
getActivity().getLayoutInflater().inflate(R.layout.esim_dialog_layout, null);
209+
final EditText activationCodeEditText =
210+
(EditText) dialogView.findViewById(R.id.activation_code);
211+
final CheckBox activateAfterDownloadCheckBox =
212+
(CheckBox) dialogView.findViewById(R.id.activate_esim);
213+
214+
new AlertDialog.Builder(getActivity())
215+
.setTitle(R.string.esim_activation_code)
216+
.setView(dialogView)
217+
.setPositiveButton(
218+
android.R.string.ok,
219+
(dialogInterface, i) -> {
220+
final String activationCodeString = activationCodeEditText.getText().toString();
221+
startEsimDownload(activationCodeString, activateAfterDownloadCheckBox.isChecked());
222+
})
223+
.setNegativeButton(android.R.string.cancel, null)
224+
.show();
225+
}
226+
227+
private void showDeleteEsimUi() {
228+
if (getActivity() == null || getActivity().isFinishing()) {
229+
return;
230+
}
231+
232+
final View dialogView =
233+
getActivity().getLayoutInflater().inflate(R.layout.simple_edittext, null);
234+
final EditText subIdEditText = dialogView.findViewById(R.id.input);
235+
236+
new AlertDialog.Builder(getActivity())
237+
.setTitle(R.string.delete_esim_dialog_title)
238+
.setView(dialogView)
239+
.setPositiveButton(
240+
android.R.string.ok,
241+
(dialogInterface, i) -> {
242+
final String subId = subIdEditText.getText().toString();
243+
deleteEsim(Integer.parseInt(subId));
244+
})
245+
.setNegativeButton(android.R.string.cancel, null)
246+
.show();
247+
}
248+
249+
private void startEsimDownload(String activationCode, boolean switchAfterDownload) {
250+
ContextCompat.registerReceiver(
251+
getActivity(),
252+
mDownloadESIMReceiver,
253+
new IntentFilter(ACTION_DOWNLOAD_ESIM),
254+
ContextCompat.RECEIVER_EXPORTED);
255+
DownloadableSubscription downloadableSubscription =
256+
DownloadableSubscription.forActivationCode(activationCode);
257+
PendingIntent pi =
258+
PendingIntent.getBroadcast(
259+
getActivity(),
260+
0,
261+
new Intent(ACTION_DOWNLOAD_ESIM),
262+
PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT);
263+
mEuiccManager.downloadSubscription(downloadableSubscription, switchAfterDownload, pi);
264+
Log.v(
265+
TAG,
266+
"started downloading eSIM, "
267+
+ "activationCode : "
268+
+ activationCode
269+
+ ", switchAfterDownload : "
270+
+ switchAfterDownload);
271+
showToast("started downloading eSIM", Toast.LENGTH_LONG);
272+
}
273+
274+
private void deleteEsim(int subId) {
275+
ContextCompat.registerReceiver(
276+
getActivity(),
277+
mDeleteESIMReceiver,
278+
new IntentFilter(ACTION_DELETE_ESIM),
279+
ContextCompat.RECEIVER_EXPORTED);
280+
PendingIntent pi =
281+
PendingIntent.getBroadcast(
282+
getActivity(),
283+
0,
284+
new Intent(),
285+
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
286+
mEuiccManager.deleteSubscription(subId, pi);
287+
288+
showToast("started deleting eSIM", Toast.LENGTH_LONG);
289+
}
290+
}

src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
450450
"credential_manager_set_blocklist";
451451
private static final String CREDENTIAL_MANAGER_CLEAR_POLICY_KEY =
452452
"credential_manager_clear_policy";
453+
private static final String MANAGE_ESIM_KEY = "manage_esim";
453454

454455
private static final String BATTERY_PLUGGED_ANY =
455456
Integer.toString(
@@ -826,6 +827,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
826827
.setOnPreferenceClickListener(this);
827828
findPreference(CREDENTIAL_MANAGER_SET_BLOCKLIST_KEY).setOnPreferenceClickListener(this);
828829
findPreference(CREDENTIAL_MANAGER_CLEAR_POLICY_KEY).setOnPreferenceClickListener(this);
830+
findPreference(MANAGE_ESIM_KEY).setOnPreferenceClickListener(this);
829831

830832
DpcPreference bindDeviceAdminPreference =
831833
(DpcPreference) findPreference(BIND_DEVICE_ADMIN_POLICIES);
@@ -1434,6 +1436,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
14341436
} else if (CREDENTIAL_MANAGER_CLEAR_POLICY_KEY.equals(key)) {
14351437
resetCredentialManagerPolicy();
14361438
return true;
1439+
} else if (MANAGE_ESIM_KEY.equals(key)) {
1440+
showFragment(new EsimControlFragment());
1441+
return true;
14371442
}
14381443
return false;
14391444
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright (C) 2024 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
19+
xmlns:tools="http://schemas.android.com/tools"
20+
android:layout_width="match_parent"
21+
android:layout_height="wrap_content">
22+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
23+
android:id="@+id/progress_layout"
24+
android:layout_width="match_parent"
25+
android:layout_height="wrap_content"
26+
android:orientation="vertical">
27+
<EditText
28+
android:id="@+id/activation_code"
29+
android:layout_width="match_parent"
30+
android:layout_height="wrap_content"
31+
android:inputType="text"
32+
android:singleLine="true" />
33+
<CheckBox
34+
android:id="@+id/activate_esim"
35+
android:layout_width="match_parent"
36+
android:layout_height="wrap_content"
37+
android:text="Activate after download" />
38+
</LinearLayout>
39+
</ScrollView>

src/main/res/values/attrs.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
<enum name="S_V2" value="32" />
3535
<enum name="T" value="33" />
3636
<enum name="U" value="34" />
37+
<!-- Change to V API level once released. -->
38+
<enum name="V" value="10000" />
3739
</attr>
3840

3941
<!-- Constrain a preference to DO or PO admins. -->

src/main/res/values/strings.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@
230230
<string name="disable_cross_profile_caller_id">Disable cross-profile caller ID</string>
231231
<string name="disable_cross_profile_contacts_search">Disable cross-profile contacts search</string>
232232

233+
<!-- Strings for telephony -->
234+
<string name="telephony_title">Telephony</string>
235+
233236
<!-- Strings for APN -->
234237
<string name="override_apn_title">Override APN management</string>
235238
<string name="manage_override_apn">Manage override APNs</string>
@@ -256,6 +259,16 @@
256259
<string name="apn_mvno_type">MVNO type</string>
257260
<string name="apn_numeric_hint">Numeric=MCC+MNC</string>
258261

262+
<!-- Strings for eSIM controls-->
263+
<string name="esim_management">eSIM Management</string>
264+
<string name="manage_esim"> Manage eSIM</string>
265+
<string name="download_esim">Download eSIM</string>
266+
<string name="esim_activation_code">Enter activation code</string>
267+
<string name="delete_esim_dialog_title">Enter subId of the eSIM to delete</string>
268+
<string name="get_managed_esim_dialog_title">List of managed eSIMs subId</string>
269+
<string name="delete_esim">Delete eSIM</string>
270+
<string name="get_managed_esim">Get managed eSIM</string>
271+
259272
<string name="apn_entry_name_cannot_be_empty">Entry name cannot be empty</string>
260273
<string name="apn_name_cannot_be_empty">APN name cannot be empty</string>
261274
<string name="apn_type_cannot_be_zero">Apn type bitmask cannot be empty or zero</string>

0 commit comments

Comments
 (0)