Merge "Migrate SIM operation dialog from LPA to Settings"

This commit is contained in:
Jiashen Wang
2020-09-29 03:17:48 +00:00
committed by Android (Google) Code Review
8 changed files with 905 additions and 0 deletions

View File

@@ -511,6 +511,11 @@
</intent-filter>
</activity>
<activity android:name=".network.telephony.ToggleSubscriptionDialogActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:theme="@style/Transparent" />
<activity
android:name="Settings$TetherSettingsActivity"
android:label="@string/tether_settings_title_all"

View File

@@ -11926,6 +11926,18 @@
<!-- See less items in contextual homepage [CHAR LIMIT=30]-->
<string name="see_less">See less</string>
<!-- Strings for toggling subscriptions dialog activity -->
<!-- Title of confirmation dialog asking the user if they want to disable subscription. [CHAR_LIMIT=NONE] -->
<string name="privileged_action_disable_sub_dialog_title">Turn off <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
<!-- Title of confirmation dialog asking the user if they want to disable subscription. [CHAR_LIMIT=NONE] -->
<string name="privileged_action_disable_sub_dialog_title_without_carrier">Turn off SIM?</string>
<!-- Disabling SIMs progress dialog message [CHAR LIMIT=NONE] -->
<string name="privileged_action_disable_sub_dialog_progress">Turning off SIM<xliff:g id="ellipsis" example="...">&#8230;</xliff:g></string>
<!-- Title of error messaging indicating the device could not disable the mobile network. [CHAR LIMIT=NONE] -->
<string name="privileged_action_disable_fail_title">Can\'t disable carrier</string>
<!-- Body text of error message indicating the device could not disable the mobile network, due to an unknown issue. [CHAR LIMIT=NONE] -->
<string name="privileged_action_disable_fail_text">Something went wrong and your carrier could not be disabled.</string>
<!-- Title for Network connection request Dialog [CHAR LIMIT=60] -->
<string name="network_connection_request_dialog_title">Connect to device</string>
<!-- Summary for Network connection request Dialog [CHAR LIMIT=NONE] -->

View File

@@ -0,0 +1,364 @@
/*
* 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;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.CallSuper;
import androidx.annotation.IntDef;
import com.android.settingslib.utils.ThreadUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* A headless fragment encapsulating a long-running action such as a network RPC surviving rotation.
*
* <p>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.
*
* <p>It is safe to update the state at any time, but state updates must originate from the main
* thread.
*
* <p>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).
*
* <p>Listeners can receive state changes for the same state/substate combination, so listeners
* should make sure to be idempotent during state change events.
*
* <p>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.
*
* <h2>Managing fragment responsibilities</h2>
*
* <ol>
* <li>Instantiates the sidecar fragment when necessary, preferably in {@link #onStart}.
* <li>Removes the sidecar fragment when it's no longer used or when itself is removed. Removal of
* the managing fragment can be detected by checking {@link #isRemoving} in {@link #onStop}.
* <br>
* <li>Registers as a listener in {@link #onResume()}, unregisters in {@link #onPause()}.
* <li>Starts the long-running operation by calling into the sidecar.
* <li>Receives state updates via {@link Listener#onStateChange(SidecarFragment)} and updates the
* UI accordingly.
* </ol>
*
* <h2>Managing fragment example</h2>
*
* <pre>
* public class MainFragment implements SidecarFragment.Listener {
* private static final String TAG_SOME_SIDECAR = ...;
* private static final String KEY_SOME_SIDECAR_STATE = ...;
*
* private SomeSidecarFragment mSidecar;
*
* &#064;Override
* public void onStart() {
* super.onStart();
* Bundle args = ...; // optional args
* mSidecar = SidecarFragment.get(getFragmentManager(), TAG_SOME_SIDECAR,
* SidecarFragment.class, args);
* }
*
* &#064;Override
* public void onResume() {
* mSomeSidecar.addListener(this);
* }
*
* &#064;Override
* public void onPause() {
* mSomeSidecar.removeListener(this):
* }
* }
* </pre>
*/
public class SidecarFragment extends Fragment {
private static final String TAG = "SidecarFragment";
/**
* Get an instance of this sidecar.
*
* <p>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 <T extends SidecarFragment> T get(
FragmentManager fm, String tag, Class<T> clazz, Bundle args) {
T fragment = (T) fm.findFragmentByTag(tag);
if (fragment == null) {
try {
fragment = clazz.newInstance();
} catch (java.lang.InstantiationException e) {
throw new InstantiationException("Unable to create fragment", e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Unable to create fragment", e);
}
if (args != null) {
fragment.setArguments(args);
}
fm.beginTransaction().add(fragment, tag).commit();
// No real harm in doing this here - get() should generally only be called from onCreate
// which is on the main thread - and it allows us to start running the sidecar on this
// instance immediately rather than having to wait until the transaction commits.
fm.executePendingTransactions();
}
return fragment;
}
/** State definitions. @see {@link #getState} */
@Retention(RetentionPolicy.SOURCE)
@IntDef({State.INIT, State.RUNNING, State.SUCCESS, State.ERROR})
public @interface State {
/** Initial idling state. */
int INIT = 0;
/** The long-running operation is in progress. */
int RUNNING = 1;
/** The long-running operation has succeeded. */
int SUCCESS = 2;
/** The long-running operation has failed. */
int ERROR = 3;
}
/** Substate definitions. @see {@link #getSubstate} */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Substate.UNUSED,
Substate.RUNNING_BIND_SERVICE,
Substate.RUNNING_GET_ACTIVATION_CODE,
})
public @interface Substate {
// Unknown/unused substate.
int UNUSED = 0;
int RUNNING_BIND_SERVICE = 1;
int RUNNING_GET_ACTIVATION_CODE = 2;
// Future tags: 3+
}
/** **************************************** */
private Set<Listener> mListeners = new CopyOnWriteArraySet<>();
// Used to track whether onCreate has been called yet.
private boolean mCreated;
@State private int mState;
@Substate private int mSubstate;
/** A listener receiving state change events. */
public interface Listener {
/**
* Called upon any state or substate change.
*
* <p>The new state can be queried through {@link #getState} and {@link #getSubstate}.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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;
}
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}