Loading AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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" Loading res/values/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -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="...">…</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] --> src/com/android/settings/SidecarFragment.java 0 → 100644 +364 −0 Original line number Diff line number Diff line /* * 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; * * @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): * } * } * </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; } } } src/com/android/settings/network/SubscriptionUtil.java +30 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java 0 → 100644 +55 −0 Original line number Diff line number Diff line /* * 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); } } Loading
AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
res/values/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -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="...">…</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] -->
src/com/android/settings/SidecarFragment.java 0 → 100644 +364 −0 Original line number Diff line number Diff line /* * 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; * * @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): * } * } * </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; } } }
src/com/android/settings/network/SubscriptionUtil.java +30 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading
src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java 0 → 100644 +55 −0 Original line number Diff line number Diff line /* * 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); } }