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.compat.PhoneAccountCompat; 47 import com.android.dialer.contacts.resources.R; 48 import com.android.dialer.location.GeoUtil; 49 import com.android.dialer.phonenumberutil.PhoneNumberHelper; 50 import com.android.dialer.protos.ProtoParsers; 51 import com.android.dialer.telecom.TelecomUtil; 52 import com.google.common.base.Optional; 53 54 /** 55 * Dialog that allows the user to select a phone accounts for a given action. Optionally provides 56 * the choice to set the phone account as default. 57 */ 58 public class SelectPhoneAccountDialogFragment extends DialogFragment { 59 60 @VisibleForTesting public static final String ARG_OPTIONS = "options"; 61 62 private static final String ARG_IS_DEFAULT_CHECKED = "is_default_checked"; 63 64 private SelectPhoneAccountDialogOptions options = 65 SelectPhoneAccountDialogOptions.getDefaultInstance(); 66 private SelectPhoneAccountListener listener; 67 68 private boolean isDefaultChecked; 69 private boolean isSelected; 70 71 /** Create new fragment instance. */ newInstance( SelectPhoneAccountDialogOptions options, SelectPhoneAccountListener listener)72 public static SelectPhoneAccountDialogFragment newInstance( 73 SelectPhoneAccountDialogOptions options, SelectPhoneAccountListener listener) { 74 SelectPhoneAccountDialogFragment fragment = new SelectPhoneAccountDialogFragment(); 75 fragment.setListener(listener); 76 Bundle arguments = new Bundle(); 77 ProtoParsers.put(arguments, ARG_OPTIONS, options); 78 fragment.setArguments(arguments); 79 return fragment; 80 } 81 setListener(SelectPhoneAccountListener listener)82 public void setListener(SelectPhoneAccountListener listener) { 83 this.listener = listener; 84 } 85 86 @Nullable 87 @VisibleForTesting getListener()88 public SelectPhoneAccountListener getListener() { 89 return listener; 90 } 91 92 @VisibleForTesting canSetDefault()93 public boolean canSetDefault() { 94 return options.getCanSetDefault(); 95 } 96 97 @Override onSaveInstanceState(Bundle outState)98 public void onSaveInstanceState(Bundle outState) { 99 super.onSaveInstanceState(outState); 100 outState.putBoolean(ARG_IS_DEFAULT_CHECKED, isDefaultChecked); 101 } 102 103 @Override onCreateDialog(Bundle savedInstanceState)104 public Dialog onCreateDialog(Bundle savedInstanceState) { 105 options = 106 ProtoParsers.getTrusted( 107 getArguments(), ARG_OPTIONS, SelectPhoneAccountDialogOptions.getDefaultInstance()); 108 if (savedInstanceState != null) { 109 isDefaultChecked = savedInstanceState.getBoolean(ARG_IS_DEFAULT_CHECKED); 110 } 111 isSelected = false; 112 113 final DialogInterface.OnClickListener selectionListener = 114 new DialogInterface.OnClickListener() { 115 @Override 116 public void onClick(DialogInterface dialog, int which) { 117 isSelected = true; 118 PhoneAccountHandle selectedAccountHandle = 119 SelectPhoneAccountDialogOptionsUtil.getPhoneAccountHandle( 120 options.getEntriesList().get(which)); 121 Bundle result = new Bundle(); 122 result.putParcelable( 123 SelectPhoneAccountListener.EXTRA_SELECTED_ACCOUNT_HANDLE, selectedAccountHandle); 124 result.putBoolean(SelectPhoneAccountListener.EXTRA_SET_DEFAULT, isDefaultChecked); 125 result.putString(SelectPhoneAccountListener.EXTRA_CALL_ID, getCallId()); 126 if (listener != null) { 127 listener.onReceiveResult(SelectPhoneAccountListener.RESULT_SELECTED, result); 128 } 129 } 130 }; 131 132 final CompoundButton.OnCheckedChangeListener checkListener = 133 new CompoundButton.OnCheckedChangeListener() { 134 @Override 135 public void onCheckedChanged(CompoundButton check, boolean isChecked) { 136 isDefaultChecked = isChecked; 137 } 138 }; 139 140 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 141 ListAdapter selectAccountListAdapter = 142 new SelectAccountListAdapter( 143 builder.getContext(), R.layout.select_account_list_item, options); 144 145 AlertDialog dialog = 146 builder 147 .setTitle( 148 options.hasTitle() ? options.getTitle() : R.string.select_account_dialog_title) 149 .setAdapter(selectAccountListAdapter, selectionListener) 150 .create(); 151 152 if (options.getCanSetDefault()) { 153 // Generate custom checkbox view, lint suppressed since no appropriate parent (is dialog) 154 @SuppressLint("InflateParams") 155 LinearLayout checkboxLayout = 156 (LinearLayout) 157 LayoutInflater.from(builder.getContext()) 158 .inflate(R.layout.default_account_checkbox, null); 159 160 CheckBox checkBox = checkboxLayout.findViewById(R.id.default_account_checkbox_view); 161 checkBox.setOnCheckedChangeListener(checkListener); 162 checkBox.setChecked(isDefaultChecked); 163 164 TextView textView = checkboxLayout.findViewById(R.id.default_account_checkbox_text); 165 int setDefaultResId = 166 options.hasSetDefaultLabel() 167 ? options.getSetDefaultLabel() 168 : R.string.set_default_account; 169 textView.setText(setDefaultResId); 170 textView.setOnClickListener((view) -> checkBox.performClick()); 171 checkboxLayout.setOnClickListener((view) -> checkBox.performClick()); 172 checkboxLayout.setContentDescription(getString(setDefaultResId)); 173 dialog.getListView().addFooterView(checkboxLayout); 174 } 175 176 return dialog; 177 } 178 179 @Override onCancel(DialogInterface dialog)180 public void onCancel(DialogInterface dialog) { 181 if (!isSelected && listener != null) { 182 Bundle result = new Bundle(); 183 result.putString(SelectPhoneAccountListener.EXTRA_CALL_ID, getCallId()); 184 listener.onReceiveResult(SelectPhoneAccountListener.RESULT_DISMISSED, result); 185 } 186 super.onCancel(dialog); 187 } 188 189 @Nullable getCallId()190 private String getCallId() { 191 return options.getCallId(); 192 } 193 194 public static class SelectPhoneAccountListener extends ResultReceiver { 195 196 static final int RESULT_SELECTED = 1; 197 static final int RESULT_DISMISSED = 2; 198 199 static final String EXTRA_SELECTED_ACCOUNT_HANDLE = "extra_selected_account_handle"; 200 static final String EXTRA_SET_DEFAULT = "extra_set_default"; 201 static final String EXTRA_CALL_ID = "extra_call_id"; 202 SelectPhoneAccountListener()203 protected SelectPhoneAccountListener() { 204 super(new Handler()); 205 } 206 207 @Override onReceiveResult(int resultCode, Bundle resultData)208 protected void onReceiveResult(int resultCode, Bundle resultData) { 209 if (resultCode == RESULT_SELECTED) { 210 onPhoneAccountSelected( 211 resultData.getParcelable(EXTRA_SELECTED_ACCOUNT_HANDLE), 212 resultData.getBoolean(EXTRA_SET_DEFAULT), 213 resultData.getString(EXTRA_CALL_ID)); 214 } else if (resultCode == RESULT_DISMISSED) { 215 onDialogDismissed(resultData.getString(EXTRA_CALL_ID)); 216 } 217 } 218 onPhoneAccountSelected( PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId)219 public void onPhoneAccountSelected( 220 PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId) {} 221 onDialogDismissed(@ullable String callId)222 public void onDialogDismissed(@Nullable String callId) {} 223 } 224 225 static class SelectAccountListAdapter 226 extends ArrayAdapter<SelectPhoneAccountDialogOptions.Entry> { 227 228 private int mResId; 229 private final SelectPhoneAccountDialogOptions options; 230 SelectAccountListAdapter( Context context, int resource, SelectPhoneAccountDialogOptions options)231 SelectAccountListAdapter( 232 Context context, int resource, SelectPhoneAccountDialogOptions options) { 233 super(context, resource, options.getEntriesList()); 234 this.options = options; 235 mResId = resource; 236 } 237 238 @Override areAllItemsEnabled()239 public boolean areAllItemsEnabled() { 240 return false; 241 } 242 243 @Override isEnabled(int position)244 public boolean isEnabled(int position) { 245 return options.getEntries(position).getEnabled(); 246 } 247 248 @Override getView(int position, View convertView, ViewGroup parent)249 public View getView(int position, View convertView, ViewGroup parent) { 250 LayoutInflater inflater = 251 (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 252 253 View rowView; 254 final ViewHolder holder; 255 256 if (convertView == null) { 257 // Cache views for faster scrolling 258 rowView = inflater.inflate(mResId, null); 259 holder = new ViewHolder(); 260 holder.labelTextView = (TextView) rowView.findViewById(R.id.label); 261 holder.numberTextView = (TextView) rowView.findViewById(R.id.number); 262 holder.hintTextView = rowView.findViewById(R.id.hint); 263 holder.imageView = (ImageView) rowView.findViewById(R.id.icon); 264 rowView.setTag(holder); 265 } else { 266 rowView = convertView; 267 holder = (ViewHolder) rowView.getTag(); 268 } 269 270 SelectPhoneAccountDialogOptions.Entry entry = getItem(position); 271 PhoneAccountHandle accountHandle = 272 SelectPhoneAccountDialogOptionsUtil.getPhoneAccountHandle(entry); 273 PhoneAccount account = 274 getContext().getSystemService(TelecomManager.class).getPhoneAccount(accountHandle); 275 if (account == null) { 276 return rowView; 277 } 278 holder.labelTextView.setText(account.getLabel()); 279 if (account.getAddress() == null 280 || TextUtils.isEmpty(account.getAddress().getSchemeSpecificPart())) { 281 holder.numberTextView.setVisibility(View.GONE); 282 } else { 283 holder.numberTextView.setVisibility(View.VISIBLE); 284 holder.numberTextView.setText( 285 PhoneNumberHelper.formatNumberForDisplay( 286 getContext(), 287 account.getAddress().getSchemeSpecificPart(), 288 getCountryIso(getContext(), accountHandle))); 289 } 290 holder.imageView.setImageDrawable( 291 PhoneAccountCompat.createIconDrawable(account, getContext())); 292 293 if (TextUtils.isEmpty(entry.getHint())) { 294 holder.hintTextView.setVisibility(View.GONE); 295 } else { 296 holder.hintTextView.setVisibility(View.VISIBLE); 297 holder.hintTextView.setText(entry.getHint()); 298 } 299 holder.labelTextView.setEnabled(entry.getEnabled()); 300 holder.numberTextView.setEnabled(entry.getEnabled()); 301 holder.hintTextView.setEnabled(entry.getEnabled()); 302 holder.imageView.setImageAlpha(entry.getEnabled() ? 255 : 97 /* 38%*/); 303 return rowView; 304 } 305 getCountryIso( Context context, @NonNull PhoneAccountHandle phoneAccountHandle)306 private static String getCountryIso( 307 Context context, @NonNull PhoneAccountHandle phoneAccountHandle) { 308 Optional<SubscriptionInfo> info = 309 TelecomUtil.getSubscriptionInfo(context, phoneAccountHandle); 310 if (!info.isPresent()) { 311 return GeoUtil.getCurrentCountryIso(context); 312 } 313 return info.get().getCountryIso().toUpperCase(); 314 } 315 316 static final class ViewHolder { 317 318 TextView labelTextView; 319 TextView numberTextView; 320 TextView hintTextView; 321 ImageView imageView; 322 } 323 } 324 } 325