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