Subclasses should implement their own state machine, updating the state on each state change + * via {@link #setState(int, int)}. They can define their own states, however, it is suggested that + * the pre-defined {@link @State} constants are used and customizations are implemented via + * substates. Custom states must be outside the range of pre-defined states. + * + *
It is safe to update the state at any time, but state updates must originate from the main + * thread. + * + *
A listener can be attached that receives state updates while it's registered. Note that state + * change events can occur at any point in time and hence a registered listener should unregister if + * it cannot act upon the state change (typically a non-resumed fragment). + * + *
Listeners can receive state changes for the same state/substate combination, so listeners + * should make sure to be idempotent during state change events. + * + *
If a SidecarFragment is only relevant during the lifetime of another fragment (for example, a + * sidecar performing a details request for a DetailsFragment), that fragment needs to become the + * managing fragment of the sidecar. + * + *
+ * public class MainFragment implements SidecarFragment.Listener { + * private static final String TAG_SOME_SIDECAR = ...; + * private static final String KEY_SOME_SIDECAR_STATE = ...; + * + * private SomeSidecarFragment mSidecar; + * + * @Override + * public void onStart() { + * super.onStart(); + * Bundle args = ...; // optional args + * mSidecar = SidecarFragment.get(getFragmentManager(), TAG_SOME_SIDECAR, + * SidecarFragment.class, args); + * } + * + * @Override + * public void onResume() { + * mSomeSidecar.addListener(this); + * } + * + * @Override + * public void onPause() { + * mSomeSidecar.removeListener(this): + * } + * } + *+ */ +public class SidecarFragment extends Fragment { + + private static final String TAG = "SidecarFragment"; + + /** + * Get an instance of this sidecar. + * + *
Will return the existing instance if one is already present. Note that the args will not
+ * be used in this situation, so args must be constant for any particular fragment manager and
+ * tag.
+ */
+ @SuppressWarnings("unchecked")
+ protected static The new state can be queried through {@link #getState} and {@link #getSubstate}.
+ *
+ * Called from the main thread.
+ *
+ * @param fragment the SidecarFragment that changed its state
+ */
+ void onStateChange(SidecarFragment fragment);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ mCreated = true;
+ setState(State.INIT, Substate.UNUSED);
+ }
+
+ @Override
+ public void onDestroy() {
+ mCreated = false;
+ super.onDestroy();
+ }
+
+ /**
+ * Registers a listener that will receive subsequent state changes.
+ *
+ * A {@link Listener#onStateChange(SidecarFragment)} event is fired as part of this call
+ * unless {@link #onCreate} has not yet been called (which means that it's unsafe to access this
+ * fragment as it has not been setup or restored completely). In that case, the future call to
+ * onCreate will trigger onStateChange on registered listener.
+ *
+ * Must be called from the main thread.
+ *
+ * @param listener a listener, or null for unregistering the current listener
+ */
+ public void addListener(Listener listener) {
+ ThreadUtils.ensureMainThread();
+ mListeners.add(listener);
+ if (mCreated) {
+ notifyListener(listener);
+ }
+ }
+
+ /**
+ * Removes a previously registered listener.
+ *
+ * @return {@code true} if the listener was removed, {@code false} if there was no such listener
+ * registered.
+ */
+ public boolean removeListener(Listener listener) {
+ ThreadUtils.ensureMainThread();
+ return mListeners.remove(listener);
+ }
+
+ /** Returns the current state. */
+ @State
+ public int getState() {
+ return mState;
+ }
+
+ /** Returns the current substate. */
+ @Substate
+ public int getSubstate() {
+ return mSubstate;
+ }
+
+ /**
+ * Resets the sidecar to its initial state.
+ *
+ * Implementers can override this method to perform additional reset tasks, but must call the
+ * super method.
+ */
+ @CallSuper
+ public void reset() {
+ setState(State.INIT, Substate.UNUSED);
+ }
+
+ /**
+ * Updates the state and substate and notifies the registered listener.
+ *
+ * Must be called from the main thread.
+ *
+ * @param state the state to transition to
+ * @param substate the substate to transition to
+ */
+ protected void setState(@State int state, @Substate int substate) {
+ ThreadUtils.ensureMainThread();
+
+ mState = state;
+ mSubstate = substate;
+ notifyAllListeners();
+ printState();
+ }
+
+ private void notifyAllListeners() {
+ for (Listener listener : mListeners) {
+ notifyListener(listener);
+ }
+ }
+
+ private void notifyListener(Listener listener) {
+ listener.onStateChange(this);
+ }
+
+ /** Prints the state of the sidecar. */
+ public void printState() {
+ StringBuilder sb =
+ new StringBuilder("SidecarFragment.setState(): Sidecar Class: ")
+ .append(getClass().getCanonicalName());
+ sb.append(", State: ");
+ switch (mState) {
+ case SidecarFragment.State.INIT:
+ sb.append("State.INIT");
+ break;
+ case SidecarFragment.State.RUNNING:
+ sb.append("State.RUNNING");
+ break;
+ case SidecarFragment.State.SUCCESS:
+ sb.append("State.SUCCESS");
+ break;
+ case SidecarFragment.State.ERROR:
+ sb.append("State.ERROR");
+ break;
+ default:
+ sb.append(mState);
+ break;
+ }
+ switch (mSubstate) {
+ case SidecarFragment.Substate.UNUSED:
+ sb.append(", Substate.UNUSED");
+ break;
+ default:
+ sb.append(", ").append(mSubstate);
+ break;
+ }
+
+ Log.v(TAG, sb.toString());
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ Locale.US,
+ "SidecarFragment[mState=%d, mSubstate=%d]: %s",
+ mState,
+ mSubstate,
+ super.toString());
+ }
+
+ /** The State of the sidecar status. */
+ public static final class States {
+ public static final States SUCCESS = States.create(State.SUCCESS, Substate.UNUSED);
+ public static final States ERROR = States.create(State.ERROR, Substate.UNUSED);
+
+ @State public final int state;
+ @Substate public final int substate;
+
+ /** Creates a new sidecar state. */
+ public static States create(@State int state, @Substate int substate) {
+ return new States(state, substate);
+ }
+
+ public States(@State int state, @Substate int substate) {
+ this.state = state;
+ this.substate = substate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof States)) {
+ return false;
+ }
+ States other = (States) o;
+ return this.state == other.state && this.substate == other.substate;
+ }
+
+ @Override
+ public int hashCode() {
+ return state * 31 + substate;
+ }
+ }
+}
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index e61cc36d37e..ac21e12c232 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -21,6 +21,7 @@ import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
import static com.android.internal.util.CollectionUtils.emptyIfNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.ParcelUuid;
import android.telephony.SubscriptionInfo;
@@ -30,6 +31,8 @@ import android.telephony.UiccSlotInfo;
import androidx.annotation.VisibleForTesting;
+import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -278,6 +281,33 @@ public class SubscriptionUtil {
}
}
+ /** Starts a dialog activity to handle SIM enabling/disabling. */
+ public static void startToggleSubscriptionDialogActivity(
+ Context context, int subId, boolean enable) {
+ context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, enable));
+ }
+
+ /**
+ * Finds and returns a subscription with a specific subscription ID.
+ * @param subscriptionManager The ProxySubscriptionManager for accessing subscription
+ * information
+ * @param subId The id of subscription to be returned
+ * @return the {@code SubscriptionInfo} whose ID is {@code subId}. It returns null if the
+ * {@code subId} is {@code SubscriptionManager.INVALID_SUBSCRIPTION_ID} or no such
+ * {@code SubscriptionInfo} is found.
+ */
+ @Nullable
+ public static SubscriptionInfo getSubById(SubscriptionManager subscriptionManager, int subId) {
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return null;
+ }
+ return subscriptionManager
+ .getAllSubscriptionInfoList()
+ .stream()
+ .filter(subInfo -> subInfo.getSubscriptionId() == subId)
+ .findFirst()
+ .get();
+ }
/**
* Whether a subscription is visible to API caller. If it's a bundled opportunistic
diff --git a/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
new file mode 100644
index 00000000000..2eaa0ec0a40
--- /dev/null
+++ b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.app.FragmentManager;
+import android.app.PendingIntent;
+
+import com.android.settings.SidecarFragment;
+import com.android.settings.network.telephony.EuiccOperationSidecar;
+
+/** A headless fragment encapsulating long-running eSIM enabling/disabling operations. */
+public class SwitchToEuiccSubscriptionSidecar extends EuiccOperationSidecar {
+ private static final String TAG = "SwitchToEuiccSubscriptionSidecar";
+ private static final String ACTION_SWITCH_TO_SUBSCRIPTION =
+ "com.android.settings.network.switchToSubscription";
+
+ private PendingIntent mCallbackIntent;
+
+ /** Returns a SwitchToEuiccSubscriptionSidecar sidecar instance. */
+ public static SwitchToEuiccSubscriptionSidecar get(FragmentManager fm) {
+ return SidecarFragment.get(
+ fm, TAG, SwitchToEuiccSubscriptionSidecar.class, null /* args */);
+ }
+
+ @Override
+ public String getReceiverAction() {
+ return ACTION_SWITCH_TO_SUBSCRIPTION;
+ }
+
+ /** Returns the pendingIntent of the eSIM operations. */
+ public PendingIntent getCallbackIntent() {
+ return mCallbackIntent;
+ }
+
+ /** Starts calling EuiccManager#switchToSubscription to enable/disable the eSIM profile. */
+ public void run(int subscriptionId) {
+ setState(State.RUNNING, Substate.UNUSED);
+ mCallbackIntent = createCallbackIntent();
+ mEuiccManager.switchToSubscription(subscriptionId, mCallbackIntent);
+ }
+}
diff --git a/src/com/android/settings/network/telephony/EuiccOperationSidecar.java b/src/com/android/settings/network/telephony/EuiccOperationSidecar.java
new file mode 100644
index 00000000000..3bda9c57c7c
--- /dev/null
+++ b/src/com/android/settings/network/telephony/EuiccOperationSidecar.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import android.Manifest;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.telephony.euicc.EuiccManager;
+import android.util.Log;
+
+import com.android.settings.SidecarFragment;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * The sidecar base class that an Euicc sidecar can extend from. The extended class should implement
+ * getReceiverAction() to return the action string for the broadcast receiver. The extended class
+ * should implement its own get() function to return an instance of that class, and implement the
+ * functional class like run() to actually trigger the function in EuiccManager.
+ */
+public abstract class EuiccOperationSidecar extends SidecarFragment {
+ private static final String TAG = "EuiccOperationSidecar";
+ private static final int REQUEST_CODE = 0;
+ private static final String EXTRA_OP_ID = "op_id";
+ private static AtomicInteger sCurrentOpId =
+ new AtomicInteger((int) SystemClock.elapsedRealtime());
+
+ protected EuiccManager mEuiccManager;
+
+ private int mResultCode;
+ private int mDetailedCode;
+ private Intent mResultIntent;
+ private int mOpId;
+
+ protected final BroadcastReceiver mReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (getReceiverAction().equals(intent.getAction())
+ && mOpId == intent.getIntExtra(EXTRA_OP_ID, -1)) {
+ mResultCode = getResultCode();
+ /* TODO: This relies on our LUI and LPA to coexist, should think about how
+ to generalize this further. */
+ mDetailedCode =
+ intent.getIntExtra(
+ EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
+ 0 /* defaultValue*/);
+ mResultIntent = intent;
+ Log.i(
+ TAG,
+ String.format(
+ "Result code : %d; detailed code : %d",
+ mResultCode, mDetailedCode));
+ onActionReceived();
+ }
+ }
+ };
+
+ /**
+ * This is called when the broadcast action is received. The subclass may override this to
+ * perform different logic. The broadcast result code may be obtained with {@link
+ * #getResultCode()} and the Intent may be obtained with {@link #getResultIntent()}.
+ */
+ protected void onActionReceived() {
+ if (mResultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
+ setState(State.SUCCESS, Substate.UNUSED);
+ } else {
+ setState(State.ERROR, mResultCode);
+ }
+ }
+
+ /**
+ * The extended class should implement it to return a string for the broadcast action. The class
+ * should be unique across all the child classes.
+ */
+ protected abstract String getReceiverAction();
+
+ protected PendingIntent createCallbackIntent() {
+ mOpId = sCurrentOpId.incrementAndGet();
+ Intent intent = new Intent(getReceiverAction());
+ intent.putExtra(EXTRA_OP_ID, mOpId);
+ return PendingIntent.getBroadcast(
+ getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mEuiccManager = (EuiccManager) getContext().getSystemService(Context.EUICC_SERVICE);
+
+ getContext()
+ .getApplicationContext()
+ .registerReceiver(
+ mReceiver,
+ new IntentFilter(getReceiverAction()),
+ Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
+ null);
+ }
+
+ @Override
+ public void onDestroy() {
+ getContext().getApplicationContext().unregisterReceiver(mReceiver);
+ super.onDestroy();
+ }
+
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ public int getDetailedCode() {
+ return mDetailedCode;
+ }
+
+ public Intent getResultIntent() {
+ return mResultIntent;
+ }
+}
diff --git a/src/com/android/settings/network/telephony/SubscriptionActionDialogActivity.java b/src/com/android/settings/network/telephony/SubscriptionActionDialogActivity.java
new file mode 100644
index 00000000000..491a776afaa
--- /dev/null
+++ b/src/com/android/settings/network/telephony/SubscriptionActionDialogActivity.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import android.R;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+
+/** The base class for subscription action dialogs */
+public class SubscriptionActionDialogActivity extends Activity {
+
+ private static final String TAG = "SubscriptionActionDialogActivity";
+
+ private ProgressDialog mProgressDialog;
+ private AlertDialog mErrorDialog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ /**
+ * Displays a loading dialog.
+ *
+ * @param message The string content should be displayed in the progress dialog.
+ */
+ protected void showProgressDialog(String message) {
+ if (mProgressDialog == null) {
+ mProgressDialog = ProgressDialog.show(this, null, message);
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.setCancelable(false);
+ }
+ mProgressDialog.setMessage(message);
+ mProgressDialog.show();
+ }
+
+ /** Dismisses the loading dialog. */
+ protected void dismissProgressDialog() {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ }
+ }
+
+ /**
+ * Displays an error dialog to indicate the subscription action failure.
+ *
+ * @param title The title of the error dialog.
+ * @param message The body text of the error dialog.
+ * @param positiveOnClickListener The callback function after users confirm with the error.
+ */
+ protected void showErrorDialog(
+ String title, String message, DialogInterface.OnClickListener positiveOnClickListener) {
+ if (mErrorDialog == null) {
+ mErrorDialog =
+ new AlertDialog.Builder(this)
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(
+ R.string.ok,
+ (dialog, which) -> {
+ positiveOnClickListener.onClick(dialog, which);
+ dismissErrorDialog();
+ })
+ .create();
+ }
+ mErrorDialog.setMessage(message);
+ mErrorDialog.show();
+ }
+
+ /** Dismisses the error dialog. */
+ protected void dismissErrorDialog() {
+ if (mErrorDialog != null) {
+ mErrorDialog.dismiss();
+ }
+ }
+}
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
new file mode 100644
index 00000000000..062c9846b2a
--- /dev/null
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.SidecarFragment;
+import com.android.settings.network.SubscriptionUtil;
+import com.android.settings.network.SwitchToEuiccSubscriptionSidecar;
+
+/** This dialog activity handles both eSIM and pSIM subscriptions enabling and disabling. */
+public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity
+ implements SidecarFragment.Listener {
+
+ private static final String TAG = "ToggleSubscriptionDialogActivity";
+
+ private static final String ARG_SUB_ID = "sub_id";
+ private static final String ARG_enable = "enable";
+
+ /**
+ * Returns an intent of ToggleSubscriptionDialogActivity.
+ * @param context The context used to start the ToggleSubscriptionDialogActivity.
+ * @param subId The subscription ID of the subscription needs to be toggled.
+ * @param enable Whether the activity should enable or disable the subscription.
+ */
+ public static Intent getIntent(Context context, int subId, boolean enable) {
+ Intent intent = new Intent(context, ToggleSubscriptionDialogActivity.class);
+ intent.putExtra(ARG_SUB_ID, subId);
+ intent.putExtra(ARG_enable, enable);
+ return intent;
+ }
+
+ private SubscriptionManager mSubscriptionManager;
+ private SubscriptionInfo mSubInfo;
+ private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar;
+ private AlertDialog mToggleSimConfirmDialog;
+ private boolean mEnable;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ int subId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ mSubscriptionManager = getSystemService(SubscriptionManager.class);
+
+ UserManager userManager = getSystemService(UserManager.class);
+ if (!userManager.isAdminUser()) {
+ Log.e(TAG, "It is not the admin user. Unable to toggle subscription.");
+ finish();
+ return;
+ }
+
+ if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
+ Log.e(TAG, "The subscription id is not usable.");
+ finish();
+ return;
+ }
+
+ mSubInfo = SubscriptionUtil.getSubById(mSubscriptionManager, subId);
+ mSwitchToEuiccSubscriptionSidecar =
+ SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
+ mEnable = intent.getBooleanExtra(ARG_enable, true);
+
+ if (mEnable) {
+ handleEnablingSubAction();
+ } else {
+ handleDisablingSubAction();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mSwitchToEuiccSubscriptionSidecar.addListener(this);
+ }
+
+ @Override
+ protected void onPause() {
+ mSwitchToEuiccSubscriptionSidecar.removeListener(this);
+ super.onPause();
+ }
+
+ @Override
+ public void onStateChange(SidecarFragment fragment) {
+ if (fragment == mSwitchToEuiccSubscriptionSidecar) {
+ handleSwitchToEuiccSubscriptionSidecarStateChange();
+ }
+ }
+
+ private void handleSwitchToEuiccSubscriptionSidecarStateChange() {
+ switch (mSwitchToEuiccSubscriptionSidecar.getState()) {
+ case SidecarFragment.State.SUCCESS:
+ Log.i(
+ TAG,
+ String.format(
+ "Successfully %s the eSIM profile.",
+ mEnable ? "enable" : "disable"));
+ mSwitchToEuiccSubscriptionSidecar.reset();
+ dismissProgressDialog();
+ finish();
+ break;
+ case SidecarFragment.State.ERROR:
+ Log.i(
+ TAG,
+ String.format(
+ "Failed to %s the eSIM profile.", mEnable ? "enable" : "disable"));
+ mSwitchToEuiccSubscriptionSidecar.reset();
+ dismissProgressDialog();
+ showErrorDialog(
+ getString(R.string.privileged_action_disable_fail_title),
+ getString(R.string.privileged_action_disable_fail_text),
+ (dialog, which) -> finish());
+ break;
+ }
+ }
+
+ /* Handles the enabling SIM action. */
+ private void handleEnablingSubAction() {
+ Log.i(TAG, "handleEnableSub");
+ // TODO(b/160819390): Implement enabling eSIM/pSIM profile.
+ }
+
+ /* Handles the disabling SIM action. */
+ private void handleDisablingSubAction() {
+ showToggleSimConfirmDialog(
+ (dialog, which) -> {
+ if (mSubInfo.isEmbedded()) {
+ Log.i(TAG, "Disabling the eSIM profile.");
+ showProgressDialog(
+ getString(R.string.privileged_action_disable_sub_dialog_progress));
+ mSwitchToEuiccSubscriptionSidecar.run(
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ return;
+ }
+ Log.i(TAG, "Disabling the pSIM profile.");
+ // TODO(b/160819390): Implement disabling pSIM profile.
+ });
+ }
+
+ /* Displays the SIM toggling confirmation dialog. */
+ private void showToggleSimConfirmDialog(
+ DialogInterface.OnClickListener positiveOnClickListener) {
+ if (mToggleSimConfirmDialog == null) {
+ mToggleSimConfirmDialog =
+ new AlertDialog.Builder(this)
+ .setTitle(getToggleSimConfirmDialogTitle())
+ .setPositiveButton(
+ R.string.yes,
+ (dialog, which) -> {
+ positiveOnClickListener.onClick(dialog, which);
+ dismissToggleSimConfirmDialog();
+ })
+ .setNegativeButton(
+ R.string.cancel,
+ (dialog, which) -> {
+ dismissToggleSimConfirmDialog();
+ finish();
+ })
+ .create();
+ }
+ mToggleSimConfirmDialog.show();
+ }
+
+ /* Dismisses the SIM toggling confirmation dialog. */
+ private void dismissToggleSimConfirmDialog() {
+ if (mToggleSimConfirmDialog != null) {
+ mToggleSimConfirmDialog.dismiss();
+ }
+ }
+
+ /* Returns the title of toggling SIM confirmation dialog. */
+ private String getToggleSimConfirmDialogTitle() {
+ if (mEnable) {
+ // TODO(b/160819390): Handle the case for enabling SIM.
+ return null;
+ }
+ return mSubInfo == null || TextUtils.isEmpty(mSubInfo.getDisplayName())
+ ? getString(R.string.privileged_action_disable_sub_dialog_title_without_carrier)
+ : getString(
+ R.string.privileged_action_disable_sub_dialog_title,
+ mSubInfo.getDisplayName());
+ }
+}