1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.contacts.common.widget; 18 19 import android.annotation.SuppressLint; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.app.DialogFragment; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.ResultReceiver; 28 import android.support.annotation.NonNull; 29 import android.support.annotation.Nullable; 30 import android.support.annotation.VisibleForTesting; 31 import android.telecom.PhoneAccount; 32 import android.telecom.PhoneAccountHandle; 33 import android.telecom.TelecomManager; 34 import android.telephony.SubscriptionInfo; 35 import android.text.TextUtils; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.ArrayAdapter; 40 import android.widget.CheckBox; 41 import android.widget.CompoundButton; 42 import android.widget.ImageView; 43 import android.widget.LinearLayout; 44 import android.widget.ListAdapter; 45 import android.widget.TextView; 46 import com.android.contacts.common.R; 47 import com.android.contacts.common.compat.PhoneAccountCompat; 48 import com.android.dialer.location.GeoUtil; 49 import com.android.dialer.phonenumberutil.PhoneNumberHelper; 50 import com.android.dialer.telecom.TelecomUtil; 51 import com.google.common.base.Optional; 52 import java.util.ArrayList; 53 import java.util.List; 54 55 /** 56 * Dialog that allows the user to select a phone accounts for a given action. Optionally provides 57 * the choice to set the phone account as default. 58 */ 59 public class SelectPhoneAccountDialogFragment extends DialogFragment { 60 61 private static final String ARG_TITLE_RES_ID = "title_res_id"; 62 private static final String ARG_CAN_SET_DEFAULT = "can_set_default"; 63 private static final String ARG_SET_DEFAULT_RES_ID = "set_default_res_id"; 64 private static final String ARG_ACCOUNT_HANDLES = "account_handles"; 65 private static final String ARG_IS_DEFAULT_CHECKED = "is_default_checked"; 66 private static final String ARG_LISTENER = "listener"; 67 private static final String ARG_CALL_ID = "call_id"; 68 private static final String ARG_HINTS = "hints"; 69 70 private List<PhoneAccountHandle> mAccountHandles; 71 private List<String> mHints; 72 private boolean mIsSelected; 73 private boolean mIsDefaultChecked; 74 private SelectPhoneAccountListener mListener; 75 SelectPhoneAccountDialogFragment()76 public SelectPhoneAccountDialogFragment() {} 77 78 /** 79 * Create new fragment instance with default title and no option to set as default. 80 * 81 * @param accountHandles The {@code PhoneAccountHandle}s available to select from. 82 * @param listener The listener for the results of the account selection. 83 */ newInstance( List<PhoneAccountHandle> accountHandles, SelectPhoneAccountListener listener, @Nullable String callId)84 public static SelectPhoneAccountDialogFragment newInstance( 85 List<PhoneAccountHandle> accountHandles, 86 SelectPhoneAccountListener listener, 87 @Nullable String callId) { 88 return newInstance( 89 R.string.select_account_dialog_title, false, 0, accountHandles, listener, callId, null); 90 } 91 92 /** 93 * Create new fragment instance. This method also allows specifying a custom title and "set 94 * default" checkbox. 95 * 96 * @param titleResId The resource ID for the string to use in the title of the dialog. 97 * @param canSetDefault {@code true} if the dialog should include an option to set the selection 98 * as the default. False otherwise. 99 * @param setDefaultResId The resource ID for the string to use in the "set as default" checkbox 100 * @param accountHandles The {@code PhoneAccountHandle}s available to select from. 101 * @param listener The listener for the results of the account selection. 102 * @param callId The callId to be passed back to the listener in {@link 103 * SelectPhoneAccountListener#EXTRA_CALL_ID} 104 * @param hints Additional information to be shown underneath the phone account to help user 105 * choose. Index must match {@code accountHandles} 106 */ newInstance( int titleResId, boolean canSetDefault, int setDefaultResId, List<PhoneAccountHandle> accountHandles, SelectPhoneAccountListener listener, @Nullable String callId, @Nullable List<String> hints)107 public static SelectPhoneAccountDialogFragment newInstance( 108 int titleResId, 109 boolean canSetDefault, 110 int setDefaultResId, 111 List<PhoneAccountHandle> accountHandles, 112 SelectPhoneAccountListener listener, 113 @Nullable String callId, 114 @Nullable List<String> hints) { 115 ArrayList<PhoneAccountHandle> accountHandlesCopy = new ArrayList<>(); 116 if (accountHandles != null) { 117 accountHandlesCopy.addAll(accountHandles); 118 } 119 SelectPhoneAccountDialogFragment fragment = new SelectPhoneAccountDialogFragment(); 120 final Bundle args = new Bundle(); 121 args.putInt(ARG_TITLE_RES_ID, titleResId); 122 args.putBoolean(ARG_CAN_SET_DEFAULT, canSetDefault); 123 if (setDefaultResId != 0) { 124 args.putInt(ARG_SET_DEFAULT_RES_ID, setDefaultResId); 125 } 126 args.putParcelableArrayList(ARG_ACCOUNT_HANDLES, accountHandlesCopy); 127 args.putParcelable(ARG_LISTENER, listener); 128 args.putString(ARG_CALL_ID, callId); 129 if (hints != null) { 130 args.putStringArrayList(ARG_HINTS, new ArrayList<>(hints)); 131 } 132 fragment.setArguments(args); 133 fragment.setListener(listener); 134 return fragment; 135 } 136 setListener(SelectPhoneAccountListener listener)137 public void setListener(SelectPhoneAccountListener listener) { 138 mListener = listener; 139 } 140 141 @Nullable 142 @VisibleForTesting getListener()143 public SelectPhoneAccountListener getListener() { 144 return mListener; 145 } 146 147 @VisibleForTesting canSetDefault()148 public boolean canSetDefault() { 149 return getArguments().getBoolean(ARG_CAN_SET_DEFAULT); 150 } 151 152 @Override onSaveInstanceState(Bundle outState)153 public void onSaveInstanceState(Bundle outState) { 154 super.onSaveInstanceState(outState); 155 outState.putBoolean(ARG_IS_DEFAULT_CHECKED, mIsDefaultChecked); 156 } 157 158 @Override onCreateDialog(Bundle savedInstanceState)159 public Dialog onCreateDialog(Bundle savedInstanceState) { 160 int titleResId = getArguments().getInt(ARG_TITLE_RES_ID); 161 boolean canSetDefault = getArguments().getBoolean(ARG_CAN_SET_DEFAULT); 162 mAccountHandles = getArguments().getParcelableArrayList(ARG_ACCOUNT_HANDLES); 163 mListener = getArguments().getParcelable(ARG_LISTENER); 164 mHints = getArguments().getStringArrayList(ARG_HINTS); 165 if (savedInstanceState != null) { 166 mIsDefaultChecked = savedInstanceState.getBoolean(ARG_IS_DEFAULT_CHECKED); 167 } 168 mIsSelected = false; 169 170 final DialogInterface.OnClickListener selectionListener = 171 new DialogInterface.OnClickListener() { 172 @Override 173 public void onClick(DialogInterface dialog, int which) { 174 mIsSelected = true; 175 PhoneAccountHandle selectedAccountHandle = mAccountHandles.get(which); 176 Bundle result = new Bundle(); 177 result.putParcelable( 178 SelectPhoneAccountListener.EXTRA_SELECTED_ACCOUNT_HANDLE, selectedAccountHandle); 179 result.putBoolean(SelectPhoneAccountListener.EXTRA_SET_DEFAULT, mIsDefaultChecked); 180 result.putString(SelectPhoneAccountListener.EXTRA_CALL_ID, getCallId()); 181 if (mListener != null) { 182 mListener.onReceiveResult(SelectPhoneAccountListener.RESULT_SELECTED, result); 183 } 184 } 185 }; 186 187 final CompoundButton.OnCheckedChangeListener checkListener = 188 new CompoundButton.OnCheckedChangeListener() { 189 @Override 190 public void onCheckedChanged(CompoundButton check, boolean isChecked) { 191 mIsDefaultChecked = isChecked; 192 } 193 }; 194 195 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 196 ListAdapter selectAccountListAdapter = 197 new SelectAccountListAdapter( 198 builder.getContext(), R.layout.select_account_list_item, mAccountHandles, mHints); 199 200 AlertDialog dialog = 201 builder 202 .setTitle(titleResId) 203 .setAdapter(selectAccountListAdapter, selectionListener) 204 .create(); 205 206 if (canSetDefault) { 207 // Generate custom checkbox view, lint suppressed since no appropriate parent (is dialog) 208 @SuppressLint("InflateParams") 209 LinearLayout checkboxLayout = 210 (LinearLayout) 211 LayoutInflater.from(builder.getContext()) 212 .inflate(R.layout.default_account_checkbox, null); 213 214 CheckBox checkBox = checkboxLayout.findViewById(R.id.default_account_checkbox_view); 215 checkBox.setOnCheckedChangeListener(checkListener); 216 checkBox.setChecked(mIsDefaultChecked); 217 218 TextView textView = checkboxLayout.findViewById(R.id.default_account_checkbox_text); 219 int setDefaultResId = 220 getArguments().getInt(ARG_SET_DEFAULT_RES_ID, R.string.set_default_account); 221 textView.setText(setDefaultResId); 222 textView.setOnClickListener((view) -> checkBox.performClick()); 223 checkboxLayout.setOnClickListener((view) -> checkBox.performClick()); 224 checkboxLayout.setContentDescription(getString(setDefaultResId)); 225 dialog.getListView().addFooterView(checkboxLayout); 226 } 227 228 return dialog; 229 } 230 231 @Override onCancel(DialogInterface dialog)232 public void onCancel(DialogInterface dialog) { 233 if (!mIsSelected && mListener != null) { 234 Bundle result = new Bundle(); 235 result.putString(SelectPhoneAccountListener.EXTRA_CALL_ID, getCallId()); 236 mListener.onReceiveResult(SelectPhoneAccountListener.RESULT_DISMISSED, result); 237 } 238 super.onCancel(dialog); 239 } 240 241 @Nullable getCallId()242 private String getCallId() { 243 return getArguments().getString(ARG_CALL_ID); 244 } 245 246 public static class SelectPhoneAccountListener extends ResultReceiver { 247 248 static final int RESULT_SELECTED = 1; 249 static final int RESULT_DISMISSED = 2; 250 251 static final String EXTRA_SELECTED_ACCOUNT_HANDLE = "extra_selected_account_handle"; 252 static final String EXTRA_SET_DEFAULT = "extra_set_default"; 253 static final String EXTRA_CALL_ID = "extra_call_id"; 254 SelectPhoneAccountListener()255 protected SelectPhoneAccountListener() { 256 super(new Handler()); 257 } 258 259 @Override onReceiveResult(int resultCode, Bundle resultData)260 protected void onReceiveResult(int resultCode, Bundle resultData) { 261 if (resultCode == RESULT_SELECTED) { 262 onPhoneAccountSelected( 263 resultData.getParcelable(EXTRA_SELECTED_ACCOUNT_HANDLE), 264 resultData.getBoolean(EXTRA_SET_DEFAULT), 265 resultData.getString(EXTRA_CALL_ID)); 266 } else if (resultCode == RESULT_DISMISSED) { 267 onDialogDismissed(resultData.getString(EXTRA_CALL_ID)); 268 } 269 } 270 onPhoneAccountSelected( PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId)271 public void onPhoneAccountSelected( 272 PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId) {} 273 onDialogDismissed(@ullable String callId)274 public void onDialogDismissed(@Nullable String callId) {} 275 } 276 277 private static class SelectAccountListAdapter extends ArrayAdapter<PhoneAccountHandle> { 278 279 private int mResId; 280 private final List<String> mHints; 281 SelectAccountListAdapter( Context context, int resource, List<PhoneAccountHandle> accountHandles, @Nullable List<String> hints)282 SelectAccountListAdapter( 283 Context context, 284 int resource, 285 List<PhoneAccountHandle> accountHandles, 286 @Nullable List<String> hints) { 287 super(context, resource, accountHandles); 288 mHints = hints; 289 mResId = resource; 290 } 291 292 @Override getView(int position, View convertView, ViewGroup parent)293 public View getView(int position, View convertView, ViewGroup parent) { 294 LayoutInflater inflater = 295 (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 296 297 View rowView; 298 final ViewHolder holder; 299 300 if (convertView == null) { 301 // Cache views for faster scrolling 302 rowView = inflater.inflate(mResId, null); 303 holder = new ViewHolder(); 304 holder.labelTextView = (TextView) rowView.findViewById(R.id.label); 305 holder.numberTextView = (TextView) rowView.findViewById(R.id.number); 306 holder.hintTextView = rowView.findViewById(R.id.hint); 307 holder.imageView = (ImageView) rowView.findViewById(R.id.icon); 308 rowView.setTag(holder); 309 } else { 310 rowView = convertView; 311 holder = (ViewHolder) rowView.getTag(); 312 } 313 314 PhoneAccountHandle accountHandle = getItem(position); 315 PhoneAccount account = 316 getContext().getSystemService(TelecomManager.class).getPhoneAccount(accountHandle); 317 if (account == null) { 318 return rowView; 319 } 320 holder.labelTextView.setText(account.getLabel()); 321 if (account.getAddress() == null 322 || TextUtils.isEmpty(account.getAddress().getSchemeSpecificPart())) { 323 holder.numberTextView.setVisibility(View.GONE); 324 } else { 325 holder.numberTextView.setVisibility(View.VISIBLE); 326 holder.numberTextView.setText( 327 PhoneNumberHelper.formatNumberForDisplay( 328 getContext(), 329 account.getAddress().getSchemeSpecificPart(), 330 getCountryIso(getContext(), accountHandle))); 331 } 332 holder.imageView.setImageDrawable( 333 PhoneAccountCompat.createIconDrawable(account, getContext())); 334 if (mHints != null && position < mHints.size()) { 335 String hint = mHints.get(position); 336 if (TextUtils.isEmpty(hint)) { 337 holder.hintTextView.setVisibility(View.GONE); 338 } else { 339 holder.hintTextView.setVisibility(View.VISIBLE); 340 holder.hintTextView.setText(hint); 341 } 342 } else { 343 holder.hintTextView.setVisibility(View.GONE); 344 } 345 346 return rowView; 347 } 348 getCountryIso( Context context, @NonNull PhoneAccountHandle phoneAccountHandle)349 private static String getCountryIso( 350 Context context, @NonNull PhoneAccountHandle phoneAccountHandle) { 351 Optional<SubscriptionInfo> info = 352 TelecomUtil.getSubscriptionInfo(context, phoneAccountHandle); 353 if (!info.isPresent()) { 354 return GeoUtil.getCurrentCountryIso(context); 355 } 356 return info.get().getCountryIso().toUpperCase(); 357 } 358 359 private static final class ViewHolder { 360 361 TextView labelTextView; 362 TextView numberTextView; 363 TextView hintTextView; 364 ImageView imageView; 365 } 366 } 367 } 368