1 /*
2  * Copyright (C) 2019 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.car.dialer.ui.common;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 
22 import androidx.annotation.NonNull;
23 import androidx.annotation.Nullable;
24 import androidx.recyclerview.widget.RecyclerView;
25 
26 import com.android.car.dialer.R;
27 import com.android.car.dialer.log.L;
28 import com.android.car.telephony.common.Contact;
29 import com.android.car.telephony.common.PhoneNumber;
30 import com.android.car.telephony.common.TelecomUtils;
31 import com.android.car.ui.AlertDialogBuilder;
32 import com.android.car.ui.recyclerview.CarUiRadioButtonListItem;
33 import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
34 import com.android.car.ui.recyclerview.CarUiRecyclerView;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /** Ui Utilities for dialer */
40 public class DialerUtils {
41 
42     private static final String TAG = "CD.DialerUtils";
43 
44     /**
45      * Callback interface for
46      * {@link DialerUtils#showPhoneNumberSelector(Context, List, PhoneNumberSelectionCallback)} and
47      * {@link DialerUtils#promptForPrimaryNumber(Context, Contact, PhoneNumberSelectionCallback)}
48      */
49     public interface PhoneNumberSelectionCallback {
50         /**
51          * Called when a phone number is chosen.
52          *
53          * @param phoneNumber The phone number
54          * @param always      Whether the user pressed "aways" or "just once"
55          */
onPhoneNumberSelected(PhoneNumber phoneNumber, boolean always)56         void onPhoneNumberSelected(PhoneNumber phoneNumber, boolean always);
57     }
58 
59     /**
60      * Shows a dialog asking the user to pick a phone number.
61      * Has buttons for selecting always or just once.
62      */
showPhoneNumberSelector(Context context, List<PhoneNumber> numbers, PhoneNumberSelectionCallback callback)63     public static void showPhoneNumberSelector(Context context,
64             List<PhoneNumber> numbers,
65             PhoneNumberSelectionCallback callback) {
66 
67         final List<PhoneNumber> selectedPhoneNumber = new ArrayList<>();
68         List<CarUiRadioButtonListItem> items = new ArrayList<>();
69         CarUiRadioButtonListItemAdapter adapter = new CarUiRadioButtonListItemAdapter(items);
70 
71         for (PhoneNumber number : numbers) {
72             CharSequence readableLabel = number.getReadableLabel(context.getResources());
73             CarUiRadioButtonListItem item = new CarUiRadioButtonListItem();
74             item.setTitle(number.isPrimary()
75                     ? context.getString(R.string.primary_number_description, readableLabel)
76                     : readableLabel);
77             item.setBody(TelecomUtils.getBidiWrappedNumber(number.getNumber()));
78             item.setOnCheckedChangeListener((i, isChecked) -> {
79                 if (isChecked) {
80                     selectedPhoneNumber.clear();
81                     selectedPhoneNumber.add(number);
82                 }
83             });
84             items.add(item);
85         }
86 
87         new AlertDialogBuilder(context)
88                 .setTitle(R.string.select_number_dialog_title)
89                 .setSingleChoiceItems(adapter, null)
90                 .setNeutralButton(R.string.select_number_dialog_just_once_button,
91                         (dialog, which) -> {
92                             if (!selectedPhoneNumber.isEmpty()) {
93                                 callback.onPhoneNumberSelected(selectedPhoneNumber.get(0), false);
94                             }
95                         })
96                 .setPositiveButton(R.string.select_number_dialog_always_button,
97                         (dialog, which) -> {
98                             if (!selectedPhoneNumber.isEmpty()) {
99                                 callback.onPhoneNumberSelected(selectedPhoneNumber.get(0), true);
100                             }
101                         })
102                 .show();
103     }
104 
105     /**
106      * Gets the primary phone number from the contact.
107      * If no primary number is set, a dialog will pop up asking the user to select one.
108      * If the user presses the "always" button, the phone number will become their
109      * primary phone number. The "always" parameter of the callback will always be false
110      * using this method.
111      */
promptForPrimaryNumber( Context context, Contact contact, PhoneNumberSelectionCallback callback)112     public static void promptForPrimaryNumber(
113             Context context,
114             Contact contact,
115             PhoneNumberSelectionCallback callback) {
116         if (contact.hasPrimaryPhoneNumber()) {
117             callback.onPhoneNumberSelected(contact.getPrimaryPhoneNumber(), false);
118         } else if (contact.getNumbers().size() == 1) {
119             callback.onPhoneNumberSelected(contact.getNumbers().get(0), false);
120         } else if (contact.getNumbers().size() > 0) {
121             showPhoneNumberSelector(context, contact.getNumbers(), (phoneNumber, always) -> {
122                 if (always) {
123                     TelecomUtils.setAsPrimaryPhoneNumber(context, phoneNumber);
124                 }
125 
126                 callback.onPhoneNumberSelected(phoneNumber, false);
127             });
128         } else {
129             L.w(TAG, "contact %s doesn't have any phone number", contact.getDisplayName());
130         }
131     }
132 
133     /**
134      * Returns true if the contact has postal address and show postal address config is true.
135      */
hasPostalAddress(Resources resources, @NonNull Contact contact)136     private static boolean hasPostalAddress(Resources resources, @NonNull Contact contact) {
137         boolean showPostalAddress = resources.getBoolean(
138                 R.bool.config_show_postal_address);
139         return showPostalAddress && (!contact.getPostalAddresses().isEmpty());
140     }
141 
142     /**
143      * Returns true if the contact has either phone number or postal address to show.
144      */
hasContactDetail(Resources resources, @Nullable Contact contact)145     public static boolean hasContactDetail(Resources resources, @Nullable Contact contact) {
146         boolean hasContactDetail = contact != null
147                 && (!contact.getNumbers().isEmpty() || DialerUtils.hasPostalAddress(
148                 resources, contact));
149         return hasContactDetail;
150     }
151 
152     /**
153      * Return the first visible item position in a {@link CarUiRecyclerView}.
154      */
getFirstVisibleItemPosition(@onNull CarUiRecyclerView carUiRecyclerView)155     public static int getFirstVisibleItemPosition(@NonNull CarUiRecyclerView carUiRecyclerView) {
156         int firstItem = carUiRecyclerView.findFirstCompletelyVisibleItemPosition();
157         if (firstItem == RecyclerView.NO_POSITION) {
158             firstItem = carUiRecyclerView.findFirstVisibleItemPosition();
159         }
160         return firstItem;
161     }
162 
163     /**
164      * Validates whether the old anchor point is still in the new data set range.
165      * If so, return the old anchor index, otherwise return 0;
166      */
validateListLimitingAnchor(int newSize, int oldAnchorIndex)167     public static int validateListLimitingAnchor(int newSize, int oldAnchorIndex) {
168         return newSize < oldAnchorIndex ? 0 : oldAnchorIndex;
169     }
170 }
171