[SIM Dialog Migration] Add SIM dialogs to support screen rotation

Adding dialog fragments to handle SIM enable, disable, rename, and
erase and make dialogs persistent over screen rotation.
Bug: 160819390
Test: Manually tested eSIM profile disabling.
Design: https://docs.google.com/document/d/1wb5_hoBkZVbkXGNWHbx4Jf61swjfxsJzkytiTzJosYo/edit?usp=sharing

Change-Id: I6b8c9a1c88eacdcbd3a5f0e466b1308ef639c225
This commit is contained in:
Jiashen Wang
2020-10-01 18:28:50 -07:00
parent e732daa386
commit 7e04453c74
6 changed files with 447 additions and 105 deletions

View File

@@ -0,0 +1,68 @@
/*
* 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.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
/** Fragment to show an alert dialog which only has the positive button. */
public class AlertDialogFragment extends BaseDialogFragment
implements DialogInterface.OnClickListener {
private static final String TAG = "AlertDialogFragment";
// Arguments
private static final String ARG_TITLE = "title";
private static final String ARG_MSG = "msg";
/**
* @param activity
* @param title
* @param msg
*/
public static void show(Activity activity, String title, String msg) {
AlertDialogFragment fragment = new AlertDialogFragment();
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, title);
arguments.putString(ARG_MSG, msg);
fragment.setArguments(arguments);
fragment.show(activity.getFragmentManager(), TAG);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder =
new AlertDialog.Builder(getContext())
.setTitle(getArguments().getString(ARG_TITLE))
.setPositiveButton(android.R.string.ok, this);
if (!TextUtils.isEmpty(getArguments().getString(ARG_MSG))) {
builder.setMessage(getArguments().getString(ARG_MSG));
}
return builder.show();
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (getActivity() != null) {
getActivity().finish();
}
super.dismiss();
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.app.Activity;
import android.app.DialogFragment;
import android.app.Fragment;
import android.os.Bundle;
import androidx.annotation.Nullable;
/**
* Base dialog fragment class with the functionality to make a fragment or an activity as a listener
* which can survive through the activity restarts.
*/
public abstract class BaseDialogFragment extends DialogFragment {
// Tags for the listener which receives event callbacks.
private static final String ARG_LISTENER_TAG = "listener_tag";
private static final String ARG_IN_CALLER_TAG = "in_caller_tag";
/**
* @param activity The caller activity or the activity attached with the fragment.
* @param listener The original caller, that is, the listener. The listener can be the fragment
* to receive event callbacks. If it is null, will use the activity to handle the event
* callback instead.
* @param callbackInterfaceClass The interface that the listener should implements.
* @param arguments The arguments bundle of the dispatcher fragment used to store the listener's
* info.
* @param tagInCaller An integer given by the listener to distinguish the action when calling
* the listener's callback function.
*/
protected static <T> void setListener(
Activity activity,
@Nullable Fragment listener,
Class<T> callbackInterfaceClass,
int tagInCaller,
Bundle arguments) {
checkValidity(activity, listener, callbackInterfaceClass);
if (listener != null && listener.getParentFragment() != null) {
throw new IllegalArgumentException("The listener must be attached to an activity.");
}
arguments.putInt(ARG_IN_CALLER_TAG, tagInCaller);
if (listener != null) {
arguments.putString(ARG_LISTENER_TAG, listener.getTag());
}
}
/**
* @param callbackInterfaceClass The interface that the listener should implements.
* @param <T> Template type.
* @return The listener class.
*/
@SuppressWarnings("unchecked")
protected <T> T getListener(Class<T> callbackInterfaceClass) {
Object listener;
String listenerTag = getArguments().getString(ARG_LISTENER_TAG);
if (listenerTag == null) {
listener = getActivity();
} else {
listener = getActivity().getFragmentManager().findFragmentByTag(listenerTag);
}
if (callbackInterfaceClass.isInstance(listener)) {
return (T) listener;
}
throw new IllegalArgumentException("The caller should implement the callback function.");
}
/** @return The tag set in the listener. */
protected int getTagInCaller() {
return getArguments().getInt(ARG_IN_CALLER_TAG);
}
private static <T> void checkValidity(
Activity activity, @Nullable Fragment listener, Class<T> callbackInterfaceClass) {
if (listener != null) {
if (!callbackInterfaceClass.isInstance(listener)) {
throw new IllegalArgumentException(
"The listener fragment should implement the callback function.");
}
} else {
if (!callbackInterfaceClass.isInstance(activity)) {
throw new IllegalArgumentException(
"The caller activity should implement the callback function.");
}
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
/** Fragment to show a confirm dialog. The caller should implement onConfirmListener. */
public class ConfirmDialogFragment extends BaseDialogFragment
implements DialogInterface.OnClickListener {
private static final String TAG = "ConfirmDialogFragment";
private static final String ARG_TITLE = "title";
private static final String ARG_MSG = "msg";
private static final String ARG_POS_BUTTON_STRING = "pos_button_string";
private static final String ARG_NEG_BUTTON_STRING = "neg_button_string";
/**
* Interface defining the method that will be invoked when the user has done with the dialog.
*/
public interface OnConfirmListener {
/**
* @param tag The tag in the caller.
* @param confirmed True if the user has clicked the positive button. False if the user has
* clicked the negative button or cancel the dialog.
*/
void onConfirm(int tag, boolean confirmed);
}
/** Displays a confirmation dialog which has confirm and cancel buttons. */
public static <T> void show(
Activity activity,
Class<T> callbackInterfaceClass,
int tagInCaller,
String title,
String msg,
String posButtonString,
String negButtonString) {
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, title);
arguments.putCharSequence(ARG_MSG, msg);
arguments.putString(ARG_POS_BUTTON_STRING, posButtonString);
arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString);
setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments);
fragment.setArguments(arguments);
fragment.show(activity.getFragmentManager(), TAG);
}
@Override
public final Dialog onCreateDialog(Bundle savedInstanceState) {
String title = getArguments().getString(ARG_TITLE);
String message = getArguments().getString(ARG_MSG);
String posBtnString = getArguments().getString(ARG_POS_BUTTON_STRING);
String negBtnString = getArguments().getString(ARG_NEG_BUTTON_STRING);
Log.i("Showing dialog with title = %s", title);
AlertDialog.Builder builder =
new AlertDialog.Builder(getContext())
.setTitle(title)
.setPositiveButton(posBtnString, this)
.setNegativeButton(negBtnString, this);
if (!TextUtils.isEmpty(message)) {
builder.setMessage(message);
}
AlertDialog dialog = builder.show();
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onClick(DialogInterface dialog, int which) {
informCaller(which == DialogInterface.BUTTON_POSITIVE);
}
@Override
public void onCancel(DialogInterface dialog) {
informCaller(false);
}
private void informCaller(boolean confirmed) {
OnConfirmListener listener = getListener(OnConfirmListener.class);
if (listener == null) {
return;
}
listener.onConfirm(getTagInCaller(), confirmed);
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
/** Fragment to show a progress dialog. */
public class ProgressDialogFragment extends DialogFragment {
private static final String ARG_TITLE = "title";
private static final String TAG = "ProgressDialogFragment";
private OnDismissCallback mDismissCallback;
// Host fragment is optional to implement this interface.
public interface OnDismissCallback {
// Action performed when the progress dialog is dismissed.
void onProgressDialogDismiss();
}
/**
* Check whether there is already a showing progress dialog. If yes and the title of the showing
* one is the same with the new coming one, just return and do nothing. If the title of the
* showing one is different from the new one, remove the showing one and create a new dialog for
* the new one. If there is no progress dialog right now, just create a new one.
*/
public static void show(FragmentManager fm, String title, OnDismissCallback dismissCallback) {
ProgressDialogFragment fragment = (ProgressDialogFragment) fm.findFragmentByTag(TAG);
if (fragment != null
&& TextUtils.equals(fragment.getArguments().getString(ARG_TITLE), title)) {
return;
}
FragmentTransaction ft = fm.beginTransaction();
if (fragment != null) {
ft.remove(fragment);
}
fragment = new ProgressDialogFragment();
fragment.setDismissCallback(dismissCallback);
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, title);
fragment.setArguments(arguments);
fragment.show(ft, TAG);
}
/**
* Called by the caller activity or fragment when the progress is finished.
*
* @param fm The fragment manager.
*/
public static void dismiss(FragmentManager fm) {
DialogFragment fragment = (DialogFragment) fm.findFragmentByTag(TAG);
if (fragment != null) {
fragment.dismiss();
}
}
@Override
@SuppressWarnings("deprecation") // ProgressDialog is deprecated but is intended UX for now
public Dialog onCreateDialog(Bundle savedInstanceState) {
ProgressDialog dialog = new ProgressDialog(getActivity());
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
dialog.setMessage(getArguments().getString(ARG_TITLE));
dialog.setOnKeyListener(
(progressDialog, keyCode, event) -> KeyEvent.KEYCODE_BACK == keyCode);
return dialog;
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mDismissCallback != null) {
mDismissCallback.onProgressDialogDismiss();
}
}
private void setDismissCallback(OnDismissCallback dismissCallback) {
mDismissCallback = dismissCallback;
}
}

View File

@@ -16,25 +16,24 @@
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;
import android.telephony.SubscriptionManager;
/** The base class for subscription action dialogs */
public class SubscriptionActionDialogActivity extends Activity {
private static final String TAG = "SubscriptionActionDialogActivity";
// Arguments
protected static final String ARG_SUB_ID = "sub_id";
private ProgressDialog mProgressDialog;
private AlertDialog mErrorDialog;
protected SubscriptionManager mSubscriptionManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSubscriptionManager = getSystemService(SubscriptionManager.class);
}
/**
@@ -43,20 +42,12 @@ public class SubscriptionActionDialogActivity extends Activity {
* @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();
ProgressDialogFragment.show(getFragmentManager(), message, null);
}
/** Dismisses the loading dialog. */
protected void dismissProgressDialog() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
ProgressDialogFragment.dismiss(getFragmentManager());
}
/**
@@ -64,31 +55,8 @@ public class SubscriptionActionDialogActivity extends Activity {
*
* @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();
}
protected void showErrorDialog(String title, String message) {
AlertDialogFragment.show(this, title, message);
}
}

View File

@@ -17,7 +17,6 @@
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;
@@ -35,15 +34,17 @@ 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 {
implements SidecarFragment.Listener, ConfirmDialogFragment.OnConfirmListener {
private static final String TAG = "ToggleSubscriptionDialogActivity";
private static final String ARG_SUB_ID = "sub_id";
// Arguments
private static final String ARG_enable = "enable";
// Dialog tags
private static final int DIALOG_TAG_DISABLE_SIM_CONFIRMATION = 1;
/**
* 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.
@@ -55,7 +56,6 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
return intent;
}
private SubscriptionManager mSubscriptionManager;
private SubscriptionInfo mSubInfo;
private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar;
private AlertDialog mToggleSimConfirmDialog;
@@ -67,7 +67,6 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
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()) {
@@ -87,10 +86,12 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
mEnable = intent.getBooleanExtra(ARG_enable, true);
if (mEnable) {
handleEnablingSubAction();
} else {
handleDisablingSubAction();
if (savedInstanceState == null) {
if (mEnable) {
handleEnablingSubAction();
} else {
showDisableSimConfirmDialog();
}
}
}
@@ -113,6 +114,31 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
}
}
@Override
public void onConfirm(int tag, boolean confirmed) {
if (!confirmed) {
finish();
return;
}
switch (tag) {
case DIALOG_TAG_DISABLE_SIM_CONFIRMATION:
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.");
break;
default:
Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
break;
}
}
private void handleSwitchToEuiccSubscriptionSidecarStateChange() {
switch (mSwitchToEuiccSubscriptionSidecar.getState()) {
case SidecarFragment.State.SUCCESS:
@@ -134,8 +160,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
dismissProgressDialog();
showErrorDialog(
getString(R.string.privileged_action_disable_fail_title),
getString(R.string.privileged_action_disable_fail_text),
(dialog, which) -> finish());
getString(R.string.privileged_action_disable_fail_text));
break;
}
}
@@ -146,45 +171,24 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
// 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();
private void showDisableSimConfirmDialog() {
String title =
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());
ConfirmDialogFragment.show(
this,
ConfirmDialogFragment.OnConfirmListener.class,
DIALOG_TAG_DISABLE_SIM_CONFIRMATION,
title,
null,
getString(R.string.yes),
getString(R.string.cancel));
}
/* Dismisses the SIM toggling confirmation dialog. */
@@ -193,17 +197,4 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
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());
}
}