• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 package com.android.car.dialer.telecom;
17 
18 import android.content.ContentResolver;
19 import android.content.ContentUris;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.database.Cursor;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.BitmapFactory.Options;
26 import android.graphics.Rect;
27 import android.net.Uri;
28 import android.provider.ContactsContract;
29 import android.provider.ContactsContract.CommonDataKinds.Phone;
30 import android.provider.ContactsContract.PhoneLookup;
31 import android.provider.Settings;
32 import android.support.annotation.Nullable;
33 import android.support.annotation.WorkerThread;
34 import android.telecom.Call;
35 import android.telephony.PhoneNumberUtils;
36 import android.telephony.TelephonyManager;
37 import android.text.TextUtils;
38 import android.text.format.DateUtils;
39 import android.util.Log;
40 import android.widget.ImageView;
41 
42 import com.android.car.apps.common.LetterTileDrawable;
43 import com.android.car.dialer.R;
44 import com.android.car.dialer.ui.CircleBitmapDrawable;
45 
46 import java.io.InputStream;
47 import java.util.Locale;
48 
49 public class TelecomUtils {
50     private final static String TAG = "Em.TelecomUtils";
51 
52     private static final String[] CONTACT_ID_PROJECTION = new String[]{
53             ContactsContract.PhoneLookup.DISPLAY_NAME,
54             ContactsContract.PhoneLookup.TYPE,
55             ContactsContract.PhoneLookup.LABEL,
56             ContactsContract.PhoneLookup._ID
57     };
58 
59     private static String sVoicemailNumber;
60     private static TelephonyManager sTelephonyManager;
61 
62     @WorkerThread
getContactPhotoFromNumber(ContentResolver contentResolver, String number)63     public static Bitmap getContactPhotoFromNumber(ContentResolver contentResolver, String number) {
64         if (number == null) {
65             return null;
66         }
67 
68         int id = getContactIdFromNumber(contentResolver, number);
69         if (id == 0) {
70             return null;
71         }
72         return getContactPhotoFromId(contentResolver, id);
73     }
74 
75     /**
76      * Return the contact id for the given contact id
77      *
78      * @param id the contact id to get the photo for
79      * @return the contact photo if it is found, null otherwise.
80      */
getContactPhotoFromId(ContentResolver contentResolver, long id)81     public static Bitmap getContactPhotoFromId(ContentResolver contentResolver, long id) {
82         Uri photoUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
83         InputStream photoDataStream = ContactsContract.Contacts.openContactPhotoInputStream(
84                 contentResolver, photoUri, true);
85 
86         Options options = new Options();
87         options.inPreferQualityOverSpeed = true;
88         // Scaling will be handled by later. We shouldn't scale multiple times to avoid
89         // quality lost due to multiple potential scaling up and down.
90         options.inScaled = false;
91 
92         Rect nullPadding = null;
93         Bitmap photo = BitmapFactory.decodeStream(photoDataStream, nullPadding, options);
94         if (photo != null) {
95             photo.setDensity(Bitmap.DENSITY_NONE);
96         }
97         return photo;
98     }
99 
100     /**
101      * Return the contact id for the given phone number.
102      *
103      * @param number Caller phone number
104      * @return the contact id if it is found, 0 otherwise.
105      */
getContactIdFromNumber(ContentResolver cr, String number)106     public static int getContactIdFromNumber(ContentResolver cr, String number) {
107         if (number == null || number.isEmpty()) {
108             return 0;
109         }
110 
111         Uri uri = Uri.withAppendedPath(
112                 ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
113                 Uri.encode(number));
114         Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null);
115 
116         try {
117             if (cursor != null && cursor.moveToFirst()) {
118                 int id = cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID));
119                 return id;
120             }
121         } finally {
122             if (cursor != null) {
123                 cursor.close();
124             }
125         }
126         return 0;
127     }
128 
129     /**
130      * Return the label for the given phone number.
131      *
132      * @param number Caller phone number
133      * @return the label if it is found, 0 otherwise.
134      */
getTypeFromNumber(Context context, String number)135     public static CharSequence getTypeFromNumber(Context context, String number) {
136         if (Log.isLoggable(TAG, Log.DEBUG)) {
137             Log.d(TAG, "getTypeFromNumber, number: " + number);
138         }
139         String defaultLabel = "";
140         if (number == null || number.isEmpty()) {
141             return defaultLabel;
142         }
143 
144         ContentResolver cr = context.getContentResolver();
145         Resources res = context.getResources();
146         Uri uri = Uri.withAppendedPath(
147                 PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
148         Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null);
149 
150         try {
151             if (cursor != null && cursor.moveToFirst()) {
152                 int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE);
153                 int type = cursor.getInt(typeColumn);
154                 int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL);
155                 String label = cursor.getString(labelColumn);
156                 CharSequence typeLabel =
157                         Phone.getTypeLabel(res, type, label);
158                 return typeLabel;
159             }
160         } finally {
161             if (cursor != null) {
162                 cursor.close();
163             }
164         }
165         return defaultLabel;
166     }
167 
getVoicemailNumber(Context context)168     public static String getVoicemailNumber(Context context) {
169         if (sVoicemailNumber == null) {
170             sVoicemailNumber = getTelephonyManager(context).getVoiceMailNumber();
171         }
172         return sVoicemailNumber;
173     }
174 
getTelephonyManager(Context context)175     public static TelephonyManager getTelephonyManager(Context context) {
176         if (sTelephonyManager == null) {
177             sTelephonyManager =
178                     (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
179         }
180         return sTelephonyManager;
181     }
182 
getFormattedNumber(Context context, String number)183     public static String getFormattedNumber(Context context, String number) {
184         if (Log.isLoggable(TAG, Log.DEBUG)) {
185             Log.d(TAG, "getFormattedNumber: " + number);
186         }
187         if (number == null) {
188             return "";
189         }
190 
191         String countryIso = getTelephonyManager(context).getSimCountryIso().toUpperCase(Locale.US);
192         if (countryIso.length() != 2) {
193             countryIso = Locale.getDefault().getCountry();
194             if (countryIso == null || countryIso.length() != 2) {
195                 countryIso = "US";
196             }
197         }
198         if (Log.isLoggable(TAG, Log.DEBUG)) {
199             Log.d(TAG, "PhoneNumberUtils.formatNumberToE16, number: "
200                     + number + ", country: " + countryIso);
201         }
202         String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso);
203         String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso);
204         formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber;
205         if (Log.isLoggable(TAG, Log.DEBUG)) {
206             Log.d(TAG, "getFormattedNumber, result: " + formattedNumber);
207         }
208         return formattedNumber;
209     }
210 
getDisplayName(Context context, UiCall call)211     public static String getDisplayName(Context context, UiCall call) {
212         // A call might get created before its children are added. In that case, the display name
213         // would go from "Unknown" to "Conference call" therefore we don't want to cache it.
214         if (call.hasChildren()) {
215             return context.getString(R.string.conference_call);
216         }
217 
218         return getDisplayName(context, call.getNumber(), call.getGatewayInfoOriginalAddress());
219     }
220 
getDisplayName(Context context, String number)221     public static String getDisplayName(Context context, String number) {
222         return getDisplayName(context, number, null);
223     }
224 
getDisplayName(Context context, String number, Uri gatewayOriginalAddress)225     private static String getDisplayName(Context context, String number,
226             Uri gatewayOriginalAddress) {
227         if (Log.isLoggable(TAG, Log.DEBUG)) {
228             Log.d(TAG, "getDisplayName: " + number
229                     + ", gatewayOriginalAddress: " + gatewayOriginalAddress);
230         }
231 
232         if (TextUtils.isEmpty(number)) {
233             return context.getString(R.string.unknown);
234         }
235         ContentResolver cr = context.getContentResolver();
236         String name;
237         if (number.equals(getVoicemailNumber(context))) {
238             name = context.getResources().getString(R.string.voicemail);
239         } else {
240             name = getContactNameFromNumber(cr, number);
241         }
242 
243         if (name == null) {
244             name = getFormattedNumber(context, number);
245         }
246         if (name == null && gatewayOriginalAddress != null) {
247             name = gatewayOriginalAddress.getSchemeSpecificPart();
248         }
249         if (name == null) {
250             name = context.getString(R.string.unknown);
251         }
252         return name;
253     }
254 
getContactNameFromNumber(ContentResolver cr, String number)255     private static String getContactNameFromNumber(ContentResolver cr, String number) {
256         Uri uri = Uri.withAppendedPath(
257                 ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
258 
259         Cursor cursor = null;
260         String name = null;
261         try {
262             cursor = cr.query(uri,
263                     new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
264             if (cursor != null && cursor.moveToFirst()) {
265                 name = cursor.getString(0);
266             }
267         } finally {
268             if (cursor != null) {
269                 cursor.close();
270             }
271         }
272         return name;
273     }
274 
275     /**
276      * @return A formatted string that has information about the phone call
277      * Possible strings:
278      * "Mobile · Dialing"
279      * "Mobile · 1:05"
280      * "Bluetooth disconnected"
281      */
getCallInfoText(Context context, UiCall call, CharSequence label)282     public static String getCallInfoText(Context context, UiCall call, CharSequence label) {
283         String text;
284         if (call.getState() == Call.STATE_ACTIVE) {
285             long duration = System.currentTimeMillis() - call.getConnectTimeMillis();
286             String durationString = DateUtils.formatElapsedTime(duration / 1000);
287             if (!TextUtils.isEmpty(durationString) && !TextUtils.isEmpty(label)) {
288                 text = context.getString(R.string.phone_label_with_info, label, durationString);
289             } else if (!TextUtils.isEmpty(durationString)) {
290                 text = durationString;
291             } else if (!TextUtils.isEmpty(label)) {
292                 text = (String) label;
293             } else {
294                 text = "";
295             }
296         } else {
297             String state = callStateToUiString(context, call.getState());
298             if (!TextUtils.isEmpty(label)) {
299                 text = context.getString(R.string.phone_label_with_info, label, state);
300             } else {
301                 text = state;
302             }
303         }
304         return text;
305     }
306 
307     /**
308      * @return A string representation of the call state that can be presented to a user.
309      */
callStateToUiString(Context context, int state)310     public static String callStateToUiString(Context context, int state) {
311         Resources res = context.getResources();
312         switch (state) {
313             case Call.STATE_ACTIVE:
314                 return res.getString(R.string.call_state_call_active);
315             case Call.STATE_HOLDING:
316                 return res.getString(R.string.call_state_hold);
317             case Call.STATE_NEW:
318             case Call.STATE_CONNECTING:
319                 return res.getString(R.string.call_state_connecting);
320             case Call.STATE_SELECT_PHONE_ACCOUNT:
321             case Call.STATE_DIALING:
322                 return res.getString(R.string.call_state_dialing);
323             case Call.STATE_DISCONNECTED:
324                 return res.getString(R.string.call_state_call_ended);
325             case Call.STATE_RINGING:
326                 return res.getString(R.string.call_state_call_ringing);
327             case Call.STATE_DISCONNECTING:
328                 return res.getString(R.string.call_state_call_ending);
329             default:
330                 throw new IllegalStateException("Unknown Call State: " + state);
331         }
332     }
333 
isNetworkAvailable(Context context)334     public static boolean isNetworkAvailable(Context context) {
335         TelephonyManager tm =
336                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
337         return tm.getNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN &&
338                 tm.getSimState() == TelephonyManager.SIM_STATE_READY;
339     }
340 
isAirplaneModeOn(Context context)341     public static boolean isAirplaneModeOn(Context context) {
342         return Settings.System.getInt(context.getContentResolver(),
343                 Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
344     }
345 
346     /**
347      * Sets a Contact bitmap on the provided image taking into account fail cases.
348      * It will attempt to load a Bitmap from the Contacts store, otherwise it will paint
349      * a the first letter of the contact name.
350      *
351      * @param number A key to have a consisten color per phone number.
352      * @return A worker task if a new one was needed to load the bitmap.
353      */
354     @Nullable
setContactBitmapAsync(Context context, final ImageView icon, final @Nullable String name, final String number)355     public static ContactBitmapWorker setContactBitmapAsync(Context context,
356             final ImageView icon, final @Nullable String name, final String number) {
357         return ContactBitmapWorker.loadBitmap(context.getContentResolver(), icon, number,
358                 bitmap -> {
359                     Resources r = icon.getResources();
360                     if (bitmap != null) {
361                         icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
362                         icon.setImageDrawable(new CircleBitmapDrawable(r, bitmap));
363                     } else {
364                         icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
365                         LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r);
366                         letterTileDrawable.setContactDetails(name, number);
367                         letterTileDrawable.setIsCircular(true);
368                         icon.setImageDrawable(letterTileDrawable);
369                     }
370                 });
371     }
372 
373 }
374