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