| /* |
| * Copyright (C) 2014 Xiao-Long Chen <chenxiaolong@cxl.epac.to> |
| * Copyright (C) 2023 The LineageOS Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dialer.lookup; |
| |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.net.Uri; |
| import android.provider.ContactsContract.Contacts; |
| import android.telephony.PhoneNumberUtils; |
| import android.telephony.TelephonyManager; |
| import android.util.JsonReader; |
| import android.util.JsonWriter; |
| import android.util.Log; |
| |
| import com.android.dialer.phonenumbercache.ContactInfo; |
| import com.android.dialer.util.DialerUtils; |
| |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.OutputStreamWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class LookupCache { |
| private static final String TAG = LookupCache.class.getSimpleName(); |
| |
| public static final String NAME = "Name"; |
| public static final String TYPE = "Type"; |
| public static final String LABEL = "Label"; |
| public static final String NUMBER = "Number"; |
| public static final String FORMATTED_NUMBER = "FormattedNumber"; |
| public static final String NORMALIZED_NUMBER = "NormalizedNumber"; |
| public static final String PHOTO_ID = "PhotoID"; |
| public static final String LOOKUP_URI = "LookupURI"; |
| |
| public static boolean hasCachedContact(Context context, String number) { |
| String normalizedNumber = formatE164(context, number); |
| if (normalizedNumber == null) { |
| return false; |
| } |
| |
| File file = getFilePath(context, normalizedNumber); |
| return file.exists(); |
| } |
| |
| public static void cacheContact(Context context, ContactInfo info) { |
| File file = getFilePath(context, info.normalizedNumber); |
| |
| if (file.exists()) { |
| file.delete(); |
| } |
| |
| FileOutputStream out = null; |
| JsonWriter writer = null; |
| |
| try { |
| out = new FileOutputStream(file); |
| writer = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); |
| writer.setIndent(" "); |
| List messages = new ArrayList(); |
| |
| writer.beginObject(); |
| if (info.name != null) { |
| writer.name(NAME).value(info.name); |
| } |
| writer.name(TYPE).value(info.type); |
| if (info.label != null) { |
| writer.name(LABEL).value(info.label); |
| } |
| if (info.number != null) { |
| writer.name(NUMBER).value(info.number); |
| } |
| if (info.formattedNumber != null) { |
| writer.name(FORMATTED_NUMBER).value(info.formattedNumber); |
| } |
| if (info.normalizedNumber != null) { |
| writer.name(NORMALIZED_NUMBER).value(info.normalizedNumber); |
| } |
| writer.name(PHOTO_ID).value(info.photoId); |
| |
| if (info.lookupUri != null) { |
| writer.name(LOOKUP_URI).value(info.lookupUri.toString()); |
| } |
| |
| // We do not save the photo URI. If there's a cached image, that |
| // will be used when the contact is retrieved. Otherwise, photoUri |
| // will be set to null. |
| |
| writer.endObject(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } finally { |
| DialerUtils.closeQuietly(writer); |
| DialerUtils.closeQuietly(out); |
| } |
| } |
| |
| public static ContactInfo getCachedContact(Context context, String number) { |
| String normalizedNumber = formatE164(context, number); |
| if (normalizedNumber == null) { |
| return null; |
| } |
| |
| File file = getFilePath(context, normalizedNumber); |
| if (!file.exists()) { |
| // Whatever is calling this should probably check anyway |
| return null; |
| } |
| |
| ContactInfo info = new ContactInfo(); |
| |
| FileInputStream in = null; |
| JsonReader reader = null; |
| |
| try { |
| in = new FileInputStream(file); |
| reader = new JsonReader(new InputStreamReader(in, StandardCharsets.UTF_8)); |
| |
| reader.beginObject(); |
| while (reader.hasNext()) { |
| String name = reader.nextName(); |
| |
| if (NAME.equals(name)) { |
| info.name = reader.nextString(); |
| } else if (TYPE.equals(name)) { |
| info.type = reader.nextInt(); |
| } else if (LABEL.equals(name)) { |
| info.label = reader.nextString(); |
| } else if (NUMBER.equals(name)) { |
| info.number = reader.nextString(); |
| } else if (FORMATTED_NUMBER.equals(name)) { |
| info.formattedNumber = reader.nextString(); |
| } else if (NORMALIZED_NUMBER.equals(name)) { |
| info.normalizedNumber = reader.nextString(); |
| } else if (PHOTO_ID.equals(name)) { |
| info.photoId = reader.nextInt(); |
| } else if (LOOKUP_URI.equals(name)) { |
| Uri lookupUri = Uri.parse(reader.nextString()); |
| |
| if (hasCachedImage(context, normalizedNumber)) { |
| // Insert cached photo URI |
| Uri image = Uri.withAppendedPath(LookupProvider.IMAGE_CACHE_URI, |
| Uri.encode(normalizedNumber)); |
| |
| String json = lookupUri.getEncodedFragment(); |
| if (json != null) { |
| try { |
| JSONObject jsonObj = new JSONObject(json); |
| jsonObj.putOpt(Contacts.PHOTO_URI, image.toString()); |
| lookupUri = lookupUri.buildUpon() |
| .encodedFragment(jsonObj.toString()) |
| .build(); |
| } catch (JSONException e) { |
| Log.e(TAG, "Failed to add image URI to json", e); |
| } |
| } |
| |
| info.photoUri = image; |
| } |
| |
| info.lookupUri = lookupUri; |
| } |
| } |
| reader.endObject(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } finally { |
| DialerUtils.closeQuietly(reader); |
| DialerUtils.closeQuietly(in); |
| } |
| |
| return info; |
| } |
| |
| public static void deleteCachedContacts(Context context) { |
| File dir = new File(context.getCacheDir(), "lookup"); |
| if (!dir.exists()) { |
| Log.v(TAG, "Lookup cache directory does not exist. Not clearing it."); |
| return; |
| } |
| |
| if (!dir.isDirectory()) { |
| Log.e(TAG, "Path " + dir + " is not a directory"); |
| return; |
| } |
| |
| File[] files = dir.listFiles(); |
| if (files != null) { |
| for (File file : files) { |
| if (file.isFile()) { |
| file.delete(); |
| } |
| } |
| } |
| } |
| |
| public static void deleteCachedContact(Context context, String normalizedNumber) { |
| File f = getFilePath(context, normalizedNumber); |
| if (f.exists()) { |
| f.delete(); |
| } |
| |
| f = getImagePath(context, normalizedNumber); |
| if (f.exists()) { |
| f.delete(); |
| } |
| } |
| |
| public static boolean hasCachedImage(Context context, String number) { |
| String normalizedNumber = formatE164(context, number); |
| if (normalizedNumber == null) { |
| return false; |
| } |
| |
| File file = getImagePath(context, normalizedNumber); |
| return file.exists(); |
| } |
| |
| public static Uri cacheImage(Context context, String normalizedNumber, Bitmap bmp) { |
| // Compress the cached images to save space |
| if (bmp == null) { |
| Log.e(TAG, "Failed to cache image"); |
| return null; |
| } |
| |
| File image = getImagePath(context, normalizedNumber); |
| FileOutputStream out = null; |
| |
| try { |
| out = new FileOutputStream(image); |
| bmp.compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, out); |
| return Uri.fromFile(image); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } finally { |
| DialerUtils.closeQuietly(out); |
| } |
| return null; |
| } |
| |
| public static Bitmap getCachedImage(Context context, String normalizedNumber) { |
| File image = getImagePath(context, normalizedNumber); |
| if (!image.exists()) { |
| return null; |
| } |
| |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inPreferredConfig = Bitmap.Config.ARGB_8888; |
| return BitmapFactory.decodeFile(image.getPath(), options); |
| } |
| |
| private static String formatE164(Context context, String number) { |
| TelephonyManager tm = context.getSystemService(TelephonyManager.class); |
| String countryIso = tm.getSimCountryIso().toUpperCase(); |
| return PhoneNumberUtils.formatNumberToE164(number, countryIso); |
| } |
| |
| private static File getFilePath(Context context, String normalizedNumber) { |
| File dir = new File(context.getCacheDir(), "lookup"); |
| if (!dir.exists()) { |
| dir.mkdirs(); |
| } |
| |
| return new File(dir, normalizedNumber + ".json"); |
| } |
| |
| public static File getImagePath(Context context, String normalizedNumber) { |
| File dir = new File(context.getCacheDir(), "lookup"); |
| if (!dir.exists()) { |
| dir.mkdirs(); |
| } |
| |
| return new File(dir, normalizedNumber + ".webp"); |
| } |
| } |