Merge "Migrate SIM operation dialog from LPA to Settings"
This commit is contained in:
@@ -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"
|
||||
|
@@ -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] -->
|
||||
|
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 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
|
||||
|
@@ -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