Merge "Migrate SIM operation dialog from LPA to Settings"
This commit is contained in:
@@ -511,6 +511,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".network.telephony.ToggleSubscriptionDialogActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
|
||||||
|
android:theme="@style/Transparent" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="Settings$TetherSettingsActivity"
|
android:name="Settings$TetherSettingsActivity"
|
||||||
android:label="@string/tether_settings_title_all"
|
android:label="@string/tether_settings_title_all"
|
||||||
|
@@ -11926,6 +11926,18 @@
|
|||||||
<!-- See less items in contextual homepage [CHAR LIMIT=30]-->
|
<!-- See less items in contextual homepage [CHAR LIMIT=30]-->
|
||||||
<string name="see_less">See less</string>
|
<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] -->
|
<!-- Title for Network connection request Dialog [CHAR LIMIT=60] -->
|
||||||
<string name="network_connection_request_dialog_title">Connect to device</string>
|
<string name="network_connection_request_dialog_title">Connect to device</string>
|
||||||
<!-- Summary for Network connection request Dialog [CHAR LIMIT=NONE] -->
|
<!-- Summary for Network connection request Dialog [CHAR LIMIT=NONE] -->
|
||||||
|
364
src/com/android/settings/SidecarFragment.java
Normal file
364
src/com/android/settings/SidecarFragment.java
Normal 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;
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,7 @@ import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
|
|||||||
|
|
||||||
import static com.android.internal.util.CollectionUtils.emptyIfNull;
|
import static com.android.internal.util.CollectionUtils.emptyIfNull;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
import android.telephony.SubscriptionInfo;
|
import android.telephony.SubscriptionInfo;
|
||||||
@@ -30,6 +31,8 @@ import android.telephony.UiccSlotInfo;
|
|||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
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
|
* Whether a subscription is visible to API caller. If it's a bundled opportunistic
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user