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