diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 76a87706d6a..a370d3542aa 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2692,10 +2692,12 @@ android:excludeFromRecents="true" android:exported="false" /> - + diff --git a/src/com/android/settings/sim/CallsSimListDialogFragment.java b/src/com/android/settings/sim/CallsSimListDialogFragment.java new file mode 100644 index 00000000000..bb5a0035fdf --- /dev/null +++ b/src/com/android/settings/sim/CallsSimListDialogFragment.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 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.sim; + +import android.content.Context; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Specialized version of SimListDialogFragment that fetches a list of SIMs which support calls. + */ +public class CallsSimListDialogFragment extends SimListDialogFragment { + @Override + protected List getCurrentSubscriptions() { + final Context context = getContext(); + final SubscriptionManager subscriptionManager = context.getSystemService( + SubscriptionManager.class); + final TelecomManager telecomManager = context.getSystemService(TelecomManager.class); + final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + final List phoneAccounts = + telecomManager.getCallCapablePhoneAccounts(); + final List result = new ArrayList<>(); + + if (phoneAccounts == null) { + return result; + } + for (PhoneAccountHandle handle : phoneAccounts) { + final PhoneAccount phoneAccount = telecomManager.getPhoneAccount(handle); + final int subId = telephonyManager.getSubIdForPhoneAccount(phoneAccount); + + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + continue; + } + result.add(subscriptionManager.getActiveSubscriptionInfo(subId)); + } + return result; + } +} diff --git a/src/com/android/settings/sim/PreferredSimDialogFragment.java b/src/com/android/settings/sim/PreferredSimDialogFragment.java new file mode 100644 index 00000000000..5b81e62e5d3 --- /dev/null +++ b/src/com/android/settings/sim/PreferredSimDialogFragment.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 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.sim; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; + +/** + * Presents a dialog asking the user if they want to update all services to use a given "preferred" + * SIM. Typically this would be used in a case where a device goes from having multiple SIMs down to + * only one. + */ +public class PreferredSimDialogFragment extends SimDialogFragment implements + DialogInterface.OnClickListener { + private static final String TAG = "PreferredSimDialogFrag"; + + public static PreferredSimDialogFragment newInstance() { + final PreferredSimDialogFragment fragment = new PreferredSimDialogFragment(); + final Bundle args = initArguments(SimDialogActivity.PREFERRED_PICK, + R.string.sim_preferred_title); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setTitle(getTitleResId()) + .setPositiveButton(R.string.yes, this) + .setNegativeButton(R.string.no, null) + .create(); + updateDialog(dialog); + return dialog; + } + + @Override + public void onClick(DialogInterface dialog, int buttonClicked) { + if (buttonClicked != DialogInterface.BUTTON_POSITIVE) { + return; + } + final SimDialogActivity activity = (SimDialogActivity) getActivity(); + final SubscriptionInfo info = getPreferredSubscription(); + if (info != null) { + activity.onSubscriptionSelected(getDialogType(), info.getSubscriptionId()); + } + } + + public SubscriptionInfo getPreferredSubscription() { + final Activity activity = getActivity(); + final int slotId = activity.getIntent().getIntExtra(SimDialogActivity.PREFERRED_SIM, + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + return getSubscriptionManager().getActiveSubscriptionInfoForSimSlotIndex(slotId); + } + + private void updateDialog(AlertDialog dialog) { + final SubscriptionInfo info = getPreferredSubscription(); + if (info == null) { + dismiss(); + return; + } + final String message = + getContext().getString(R.string.sim_preferred_message, info.getDisplayName()); + dialog.setMessage(message); + } + + @Override + public void updateDialog() { + updateDialog((AlertDialog) getDialog()); + } + + @VisibleForTesting + protected SubscriptionManager getSubscriptionManager() { + return getContext().getSystemService(SubscriptionManager.class); + } +} diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java index 487dace2fd0..d721efdf47a 100644 --- a/src/com/android/settings/sim/SimDialogActivity.java +++ b/src/com/android/settings/sim/SimDialogActivity.java @@ -16,37 +16,30 @@ package com.android.settings.sim; -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; +import android.content.Intent; import android.os.Bundle; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; -import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.TextView; +import android.util.Log; import android.widget.Toast; -import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import com.android.settings.R; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -public class SimDialogActivity extends Activity { +/** + * This activity provides singleton semantics per dialog type for showing various kinds of + * dialogs asking the user to make choices about which SIM to use for various services + * (calls, SMS, and data). + */ +public class SimDialogActivity extends FragmentActivity { private static String TAG = "SimDialogActivity"; public static String PREFERRED_SIM = "preferred_sim"; @@ -60,276 +53,118 @@ public class SimDialogActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final int dialogType = getIntent().getIntExtra(DIALOG_TYPE_KEY, INVALID_PICK); + showOrUpdateDialog(); + } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + showOrUpdateDialog(); + } + + private void showOrUpdateDialog() { + final int dialogType = getIntent().getIntExtra(DIALOG_TYPE_KEY, INVALID_PICK); + final String tag = Integer.toString(dialogType); + final FragmentManager fragmentManager = getSupportFragmentManager(); + SimDialogFragment fragment = (SimDialogFragment) fragmentManager.findFragmentByTag(tag); + + if (fragment == null) { + fragment = createFragment(dialogType); + fragment.show(fragmentManager, tag); + } else { + fragment.updateDialog(); + } + } + + private SimDialogFragment createFragment(int dialogType) { + switch(dialogType) { + case DATA_PICK: + return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_data, + false /* includeAskEveryTime */); + case CALLS_PICK: + return CallsSimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_calls, + true /* includeAskEveryTime */); + case SMS_PICK: + return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms, + false /* includeAskEveryTime */); + case PREFERRED_PICK: + if (!getIntent().hasExtra(PREFERRED_SIM)) { + throw new IllegalArgumentException("Missing required extra " + PREFERRED_SIM); + } + return PreferredSimDialogFragment.newInstance(); + default: + throw new IllegalArgumentException( "Invalid dialog type " + dialogType + " sent."); + } + } + + public void onSubscriptionSelected(int dialogType, int subId) { + if (getSupportFragmentManager().findFragmentByTag(Integer.toString(dialogType)) == null) { + Log.w(TAG, "onSubscriptionSelected ignored because stored fragment was null"); + return; + } switch (dialogType) { case DATA_PICK: + setDefaultDataSubId(subId); + break; case CALLS_PICK: + setDefaultCallsSubId(subId); + break; case SMS_PICK: - createDialog(this, dialogType).show(); + setDefaultSmsSubId(subId); break; case PREFERRED_PICK: - displayPreferredDialog(getIntent().getIntExtra(PREFERRED_SIM, 0)); + setPreferredSim(subId); break; default: - throw new IllegalArgumentException("Invalid dialog type " + dialogType + " sent."); - } - - } - - private void displayPreferredDialog(final int slotId) { - final Resources res = getResources(); - final Context context = getApplicationContext(); - final SubscriptionInfo sir = SubscriptionManager.from(context) - .getActiveSubscriptionInfoForSimSlotIndex(slotId); - - if (sir != null) { - AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); - alertDialogBuilder.setTitle(R.string.sim_preferred_title); - alertDialogBuilder.setMessage(res.getString( - R.string.sim_preferred_message, sir.getDisplayName())); - - alertDialogBuilder.setPositiveButton(R.string.yes, new - DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - final int subId = sir.getSubscriptionId(); - PhoneAccountHandle phoneAccountHandle = - subscriptionIdToPhoneAccountHandle(subId); - setDefaultDataSubId(context, subId); - setDefaultSmsSubId(context, subId); - setUserSelectedOutgoingPhoneAccount(phoneAccountHandle); - finish(); - } - }); - alertDialogBuilder.setNegativeButton(R.string.no, new - DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog,int id) { - finish(); - } - }); - - alertDialogBuilder.create().show(); - } else { - finish(); + throw new IllegalArgumentException( + "Invalid dialog type " + dialogType + " sent."); } } - private static void setDefaultDataSubId(final Context context, final int subId) { - final SubscriptionManager subscriptionManager = SubscriptionManager.from(context); - final TelephonyManager telephonyManager = TelephonyManager.from(context) - .createForSubscriptionId(subId); + public void onFragmentDismissed(SimDialogFragment simDialogFragment) { + final List fragments = getSupportFragmentManager().getFragments(); + if (fragments.size() == 1 && fragments.get(0) == simDialogFragment) { + finishAndRemoveTask(); + } + } + + private void setDefaultDataSubId(final int subId) { + final SubscriptionManager subscriptionManager = getSystemService(SubscriptionManager.class); + final TelephonyManager telephonyManager = getSystemService( + TelephonyManager.class).createForSubscriptionId(subId); subscriptionManager.setDefaultDataSubId(subId); telephonyManager.setDataEnabled(true); - Toast.makeText(context, R.string.data_switch_started, Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.data_switch_started, Toast.LENGTH_LONG).show(); } - private static void setDefaultSmsSubId(final Context context, final int subId) { - final SubscriptionManager subscriptionManager = SubscriptionManager.from(context); - subscriptionManager.setDefaultSmsSubId(subId); - } - - private void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle phoneAccount) { - final TelecomManager telecomManager = TelecomManager.from(this); + private void setDefaultCallsSubId(final int subId) { + final PhoneAccountHandle phoneAccount = subscriptionIdToPhoneAccountHandle(subId); + final TelecomManager telecomManager = getSystemService(TelecomManager.class); telecomManager.setUserSelectedOutgoingPhoneAccount(phoneAccount); } - private PhoneAccountHandle subscriptionIdToPhoneAccountHandle(final int subId) { - final TelecomManager telecomManager = TelecomManager.from(this); - final TelephonyManager telephonyManager = TelephonyManager.from(this); - final Iterator phoneAccounts = - telecomManager.getCallCapablePhoneAccounts().listIterator(); + private void setDefaultSmsSubId(final int subId) { + final SubscriptionManager subscriptionManager = getSystemService(SubscriptionManager.class); + subscriptionManager.setDefaultSmsSubId(subId); + } - while (phoneAccounts.hasNext()) { - final PhoneAccountHandle phoneAccountHandle = phoneAccounts.next(); - final PhoneAccount phoneAccount = telecomManager.getPhoneAccount(phoneAccountHandle); + private void setPreferredSim(final int subId) { + setDefaultDataSubId(subId); + setDefaultSmsSubId(subId); + setDefaultCallsSubId(subId); + } + + private PhoneAccountHandle subscriptionIdToPhoneAccountHandle(final int subId) { + final TelecomManager telecomManager = getSystemService(TelecomManager.class); + final TelephonyManager telephonyManager = getSystemService(TelephonyManager.class); + + for (PhoneAccountHandle handle : telecomManager.getCallCapablePhoneAccounts()) { + final PhoneAccount phoneAccount = telecomManager.getPhoneAccount(handle); if (subId == telephonyManager.getSubIdForPhoneAccount(phoneAccount)) { - return phoneAccountHandle; + return handle; } } - return null; } - - public Dialog createDialog(final Context context, final int id) { - final ArrayList list = new ArrayList(); - final SubscriptionManager subscriptionManager = SubscriptionManager.from(context); - final List subInfoList = - subscriptionManager.getActiveSubscriptionInfoList(true); - final int selectableSubInfoLength = subInfoList == null ? 0 : subInfoList.size(); - - final DialogInterface.OnClickListener selectionListener = - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int value) { - - final SubscriptionInfo sir; - - switch (id) { - case DATA_PICK: - sir = subInfoList.get(value); - setDefaultDataSubId(context, sir.getSubscriptionId()); - break; - case CALLS_PICK: - final TelecomManager telecomManager = - TelecomManager.from(context); - final List phoneAccountsList = - telecomManager.getCallCapablePhoneAccounts(); - setUserSelectedOutgoingPhoneAccount( - value < 1 ? null : phoneAccountsList.get(value - 1)); - break; - case SMS_PICK: - sir = subInfoList.get(value); - setDefaultSmsSubId(context, sir.getSubscriptionId()); - break; - default: - throw new IllegalArgumentException("Invalid dialog type " - + id + " in SIM dialog."); - } - - finish(); - } - }; - - Dialog.OnKeyListener keyListener = new Dialog.OnKeyListener() { - @Override - public boolean onKey(DialogInterface arg0, int keyCode, - KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - finish(); - } - return true; - } - }; - - ArrayList callsSubInfoList = new ArrayList(); - if (id == CALLS_PICK) { - final TelecomManager telecomManager = TelecomManager.from(context); - final TelephonyManager telephonyManager = TelephonyManager.from(context); - final Iterator phoneAccounts = - telecomManager.getCallCapablePhoneAccounts().listIterator(); - - list.add(getResources().getString(R.string.sim_calls_ask_first_prefs_title)); - callsSubInfoList.add(null); - while (phoneAccounts.hasNext()) { - final PhoneAccount phoneAccount = - telecomManager.getPhoneAccount(phoneAccounts.next()); - list.add((String)phoneAccount.getLabel()); - int subId = telephonyManager.getSubIdForPhoneAccount(phoneAccount); - if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - final SubscriptionInfo sir = SubscriptionManager.from(context) - .getActiveSubscriptionInfo(subId); - callsSubInfoList.add(sir); - } else { - callsSubInfoList.add(null); - } - } - } else { - for (int i = 0; i < selectableSubInfoLength; ++i) { - final SubscriptionInfo sir = subInfoList.get(i); - CharSequence displayName = sir.getDisplayName(); - if (displayName == null) { - displayName = ""; - } - list.add(displayName.toString()); - } - } - - String[] arr = list.toArray(new String[0]); - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - - ListAdapter adapter = new SelectAccountListAdapter( - id == CALLS_PICK ? callsSubInfoList : subInfoList, - builder.getContext(), - R.layout.select_account_list_item, - arr, id); - - switch (id) { - case DATA_PICK: - builder.setTitle(R.string.select_sim_for_data); - break; - case CALLS_PICK: - builder.setTitle(R.string.select_sim_for_calls); - break; - case SMS_PICK: - builder.setTitle(R.string.select_sim_for_sms); - break; - default: - throw new IllegalArgumentException("Invalid dialog type " - + id + " in SIM dialog."); - } - - Dialog dialog = builder.setAdapter(adapter, selectionListener).create(); - dialog.setOnKeyListener(keyListener); - - dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialogInterface) { - finish(); - } - }); - - return dialog; - - } - - private class SelectAccountListAdapter extends ArrayAdapter { - private Context mContext; - private int mResId; - private int mDialogId; - private final float OPACITY = 0.54f; - private List mSubInfoList; - - public SelectAccountListAdapter(List subInfoList, - Context context, int resource, String[] arr, int dialogId) { - super(context, resource, arr); - mContext = context; - mResId = resource; - mDialogId = dialogId; - mSubInfoList = subInfoList; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) - mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View rowView; - final ViewHolder holder; - - if (convertView == null) { - // Cache views for faster scrolling - rowView = inflater.inflate(mResId, null); - holder = new ViewHolder(); - holder.title = (TextView) rowView.findViewById(R.id.title); - holder.summary = (TextView) rowView.findViewById(R.id.summary); - holder.icon = (ImageView) rowView.findViewById(R.id.icon); - rowView.setTag(holder); - } else { - rowView = convertView; - holder = (ViewHolder) rowView.getTag(); - } - - final SubscriptionInfo sir = mSubInfoList.get(position); - if (sir == null) { - holder.title.setText(getItem(position)); - holder.summary.setText(""); - holder.icon.setImageDrawable(getResources() - .getDrawable(R.drawable.ic_live_help)); - holder.icon.setAlpha(OPACITY); - } else { - holder.title.setText(sir.getDisplayName()); - holder.summary.setText(sir.getNumber()); - holder.icon.setImageBitmap(sir.createIconBitmap(mContext)); - } - return rowView; - } - - private class ViewHolder { - TextView title; - TextView summary; - ImageView icon; - } - } } diff --git a/src/com/android/settings/sim/SimDialogFragment.java b/src/com/android/settings/sim/SimDialogFragment.java new file mode 100644 index 00000000000..10815fd7679 --- /dev/null +++ b/src/com/android/settings/sim/SimDialogFragment.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 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.sim; + +import android.content.DialogInterface; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; + +/** Common functionality for showing a dialog in SimDialogActivity. */ +public abstract class SimDialogFragment extends DialogFragment { + private static final String TAG = "SimDialogFragment"; + + private static final String KEY_TITLE_ID = "title_id"; + private static final String KEY_DIALOG_TYPE = "dialog_type"; + + protected static Bundle initArguments(int dialogType, int titleResId) { + final Bundle args = new Bundle(); + args.putInt(KEY_DIALOG_TYPE, dialogType); + args.putInt(KEY_TITLE_ID, titleResId); + return args; + } + + public int getDialogType() { + return getArguments().getInt(KEY_DIALOG_TYPE); + } + + public int getTitleResId() { + return getArguments().getInt(KEY_TITLE_ID); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + super.onDismiss(dialog); + final SimDialogActivity activity = (SimDialogActivity) getActivity(); + if (activity != null && !activity.isFinishing()) { + activity.onFragmentDismissed(this); + } + } + + public abstract void updateDialog(); +} diff --git a/src/com/android/settings/sim/SimListDialogFragment.java b/src/com/android/settings/sim/SimListDialogFragment.java new file mode 100644 index 00000000000..f78c4e76e44 --- /dev/null +++ b/src/com/android/settings/sim/SimListDialogFragment.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2019 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.sim; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.Utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Shows a dialog consisting of a list of SIMs (aka subscriptions), possibly including an additional + * entry indicating "ask me every time". + */ +public class SimListDialogFragment extends SimDialogFragment implements + DialogInterface.OnClickListener { + protected static final String KEY_INCLUDE_ASK_EVERY_TIME = "include_ask_every_time"; + + protected SelectSubscriptionAdapter mAdapter; + @VisibleForTesting + List mSubscriptions; + + public static SimListDialogFragment newInstance(int dialogType, int titleResId, + boolean includeAskEveryTime) { + final SimListDialogFragment fragment = new SimListDialogFragment(); + final Bundle args = initArguments(dialogType, titleResId); + args.putBoolean(KEY_INCLUDE_ASK_EVERY_TIME, includeAskEveryTime); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + mSubscriptions = new ArrayList<>(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(getTitleResId()); + + mAdapter = new SelectSubscriptionAdapter(builder.getContext(), mSubscriptions); + + setAdapter(builder); + final Dialog dialog = builder.create(); + updateDialog(); + return dialog; + } + + @Override + public void onClick(DialogInterface dialog, int selectionIndex) { + if (selectionIndex >= 0 && selectionIndex < mSubscriptions.size()) { + int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + final SubscriptionInfo subscription = mSubscriptions.get(selectionIndex); + if (subscription != null) { + subId = subscription.getSubscriptionId(); + } + final SimDialogActivity activity = (SimDialogActivity) getActivity(); + activity.onSubscriptionSelected(getDialogType(), subId); + } + } + + protected List getCurrentSubscriptions() { + final SubscriptionManager manager = getContext().getSystemService( + SubscriptionManager.class); + return manager.getActiveSubscriptionInfoList(true); + } + + @Override + public void updateDialog() { + List currentSubscriptions = getCurrentSubscriptions(); + if (currentSubscriptions == null) { + dismiss(); + return; + } + if (getArguments().getBoolean(KEY_INCLUDE_ASK_EVERY_TIME)) { + final List tmp = new ArrayList<>(currentSubscriptions.size() + 1); + tmp.add(null); + tmp.addAll(currentSubscriptions); + currentSubscriptions = tmp; + } + if (currentSubscriptions.equals(mSubscriptions)) { + return; + } + mSubscriptions.clear(); + mSubscriptions.addAll(currentSubscriptions); + mAdapter.notifyDataSetChanged(); + } + + @VisibleForTesting + void setAdapter(AlertDialog.Builder builder) { + builder.setAdapter(mAdapter, this); + } + + private static class SelectSubscriptionAdapter extends BaseAdapter { + private Context mContext; + private LayoutInflater mInflater; + List mSubscriptions; + + public SelectSubscriptionAdapter(Context context, List subscriptions) { + mSubscriptions = subscriptions; + mContext = context; + } + + @Override + public int getCount() { + return mSubscriptions.size(); + } + + @Override + public SubscriptionInfo getItem(int position) { + return mSubscriptions.get(position); + } + + @Override + public long getItemId(int position) { + final SubscriptionInfo info = mSubscriptions.get(position); + if (info == null) { + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + return info.getSubscriptionId(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + if (mInflater == null) { + mInflater = LayoutInflater.from(parent.getContext()); + } + convertView = mInflater.inflate(R.layout.select_account_list_item, parent, false); + } + final SubscriptionInfo sub = getItem(position); + + final TextView title = convertView.findViewById(R.id.title); + final TextView summary = convertView.findViewById(R.id.summary); + final ImageView icon = convertView.findViewById(R.id.icon); + + if (sub == null) { + title.setText(R.string.sim_calls_ask_first_prefs_title); + summary.setText(""); + icon.setImageDrawable(mContext.getDrawable(R.drawable.ic_help)); + icon.setImageTintList( + Utils.getColorAttr(mContext, android.R.attr.textColorSecondary)); + } else { + title.setText(sub.getDisplayName()); + summary.setText(sub.getNumber()); + icon.setImageBitmap(sub.createIconBitmap(mContext)); + + } + return convertView; + } + } +} diff --git a/tests/robotests/src/com/android/settings/sim/PreferredSimDialogFragmentTest.java b/tests/robotests/src/com/android/settings/sim/PreferredSimDialogFragmentTest.java new file mode 100644 index 00000000000..0b85c37aae9 --- /dev/null +++ b/tests/robotests/src/com/android/settings/sim/PreferredSimDialogFragmentTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2019 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.sim; + +import static com.android.settings.sim.SimDialogActivity.PREFERRED_PICK; +import static com.android.settings.sim.SimDialogActivity.PREFERRED_SIM; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.DialogInterface; + +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowAlertDialogCompat.class) +public class PreferredSimDialogFragmentTest extends + SimDialogFragmentTestBase { + + @Override + public void setUp() { + super.setUp(); + setDialogType(PREFERRED_PICK); + mFragment = spy(PreferredSimDialogFragment.newInstance()); + doReturn(mSubscriptionManager).when(mFragment).getSubscriptionManager(); + } + + @Test + public void onCreateDialog_noSims_dismissed() { + when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt())) + .thenReturn(null); + mIntent.putExtra(PREFERRED_SIM, 0); + startDialog(); + verify(mFragment).dismiss(); + } + + @Test + public void onCreateDialog_oneSimWrongSlotArgument_dismissed() { + when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(1)).thenReturn(null); + mIntent.putExtra(PREFERRED_SIM, 1); + startDialog(); + verify(mFragment).dismiss(); + } + + @Test + public void onCreateDialog_twoSimsSelectFirst_correctMessage() { + mIntent.putExtra(PREFERRED_SIM, 0); + + final AlertDialog alertDialog = startDialog(); + final ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(alertDialog); + final String message = (String) shadowDialog.getMessage(); + assertThat(message).contains(SIM1_NAME); + assertThat(message).doesNotContain(SIM2_NAME); + } + + @Test + public void onCreateDialog_twoSimsSelectSecond_correctMessage() { + mIntent.putExtra(PREFERRED_SIM, 1); + + final AlertDialog alertDialog = startDialog(); + final ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(alertDialog); + final String message = (String) shadowDialog.getMessage(); + assertThat(message).contains(SIM2_NAME); + assertThat(message).doesNotContain(SIM1_NAME); + } + + @Test + public void onClick_yesClicked_callsOnSubscriptionSelected() { + mIntent.putExtra(PREFERRED_SIM, 0); + + final AlertDialog alertDialog = startDialog(); + + final SimDialogActivity activity = (SimDialogActivity) spy(mFragment.getActivity()); + doReturn(activity).when(mFragment).getActivity(); + doNothing().when(activity).onSubscriptionSelected(anyInt(), anyInt()); + + mFragment.onClick(alertDialog, DialogInterface.BUTTON_POSITIVE); + verify(activity).onSubscriptionSelected(PREFERRED_PICK, SIM1_ID); + } + + @Test + public void onClick_noClicked_doesNotCallOnSubscriptionSelected() { + mIntent.putExtra(PREFERRED_SIM, 0); + + final AlertDialog alertDialog = startDialog(); + + final SimDialogActivity activity = (SimDialogActivity) spy(mFragment.getActivity()); + doReturn(activity).when(mFragment).getActivity(); + doNothing().when(activity).onSubscriptionSelected(anyInt(), anyInt()); + + mFragment.onClick(alertDialog, DialogInterface.BUTTON_NEGATIVE); + verify(activity, never()).onSubscriptionSelected(anyInt(), anyInt()); + } +} diff --git a/tests/robotests/src/com/android/settings/sim/SimDialogFragmentTestBase.java b/tests/robotests/src/com/android/settings/sim/SimDialogFragmentTestBase.java new file mode 100644 index 00000000000..904b8313642 --- /dev/null +++ b/tests/robotests/src/com/android/settings/sim/SimDialogFragmentTestBase.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019 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.sim; + +import static com.android.settings.sim.SimDialogActivity.DIALOG_TYPE_KEY; + +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; + +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.androidx.fragment.FragmentController; + +public abstract class SimDialogFragmentTestBase { + protected static final int SIM1_ID = 111; + protected static final int SIM2_ID = 222; + protected static final String SIM1_NAME = "sim111"; + protected static final String SIM2_NAME = "sim222"; + + @Mock + protected SubscriptionManager mSubscriptionManager; + @Mock + protected SubscriptionInfo mSim1; + @Mock + protected SubscriptionInfo mSim2; + + protected T mFragment; + protected Intent mIntent; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mIntent = new Intent(); + + when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(0)).thenReturn(mSim1); + when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(1)).thenReturn(mSim2); + + when(mSim1.getSubscriptionId()).thenReturn(SIM1_ID); + when(mSim1.getDisplayName()).thenReturn(SIM1_NAME); + when(mSim2.getSubscriptionId()).thenReturn(SIM2_ID); + when(mSim2.getDisplayName()).thenReturn(SIM2_NAME); + } + + protected void setDialogType(int dialogType) { + mIntent.putExtra(DIALOG_TYPE_KEY, dialogType); + } + + protected AlertDialog startDialog() { + final FragmentController controller = FragmentController.of(mFragment, + SimDialogActivity.class, mIntent); + controller.create(0 /* containerViewId */, null /* bundle */).start().visible(); + return ShadowAlertDialogCompat.getLatestAlertDialog(); + } +} diff --git a/tests/robotests/src/com/android/settings/sim/SimListDialogFragmentTest.java b/tests/robotests/src/com/android/settings/sim/SimListDialogFragmentTest.java new file mode 100644 index 00000000000..2b33ebe6c20 --- /dev/null +++ b/tests/robotests/src/com/android/settings/sim/SimListDialogFragmentTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 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.sim; + +import static com.android.settings.sim.SimDialogActivity.DATA_PICK; +import static com.android.settings.sim.SimDialogActivity.SMS_PICK; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.telephony.SubscriptionManager; + +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowAlertDialogCompat.class) +public class SimListDialogFragmentTest extends SimDialogFragmentTestBase { + + @Test + public void onCreateDialog_noSubscriptions_dismissed() { + final int dialogType = DATA_PICK; + setDialogType(dialogType); + mFragment = spy(SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_data, + false /* includeAskEveryTime */)); + doReturn(null).when(mFragment).getCurrentSubscriptions(); + startDialog(); + verify(mFragment).dismiss(); + } + + @Test + public void onCreateDialog_twoSubscriptionsNoAskEveryTime_twoSubsForDisplay() { + final int dialogType = DATA_PICK; + setDialogType(dialogType); + mFragment = spy(SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_data, + false /* includeAskEveryTime */)); + doReturn(Arrays.asList(mSim1, mSim2)).when(mFragment).getCurrentSubscriptions(); + // Avoid problems robolectric has with our real adapter. + doNothing().when(mFragment).setAdapter(any()); + final AlertDialog alertDialog = startDialog(); + assertThat(mFragment.mSubscriptions).hasSize(2); + + final SimDialogActivity activity = (SimDialogActivity) spy(mFragment.getActivity()); + doReturn(activity).when(mFragment).getActivity(); + doNothing().when(activity).onSubscriptionSelected(anyInt(), anyInt()); + + mFragment.onClick(alertDialog, 1); + verify(activity).onSubscriptionSelected(dialogType, SIM2_ID); + } + + @Test + public void onCreateDialog_twoSubscriptionsAskEveryTime_threeSubsForDisplay() { + final int dialogType = SMS_PICK; + setDialogType(dialogType); + mFragment = spy(SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms, + true /* includeAskEveryTime */)); + doReturn(Arrays.asList(mSim1, mSim2)).when(mFragment).getCurrentSubscriptions(); + // Avoid problems robolectric has with our real adapter. + doNothing().when(mFragment).setAdapter(any()); + final AlertDialog alertDialog = startDialog(); + assertThat(mFragment.mSubscriptions).hasSize(3); + assertThat(mFragment.mSubscriptions.get(0)).isNull(); + + final SimDialogActivity activity = (SimDialogActivity) spy(mFragment.getActivity()); + doReturn(activity).when(mFragment).getActivity(); + doNothing().when(activity).onSubscriptionSelected(anyInt(), anyInt()); + + mFragment.onClick(alertDialog, 0); + verify(activity).onSubscriptionSelected(dialogType, + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } +}