• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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