@Override public void onBindViewHolder(GiphyViewHolder holder, int position) { GiphyImage image = images.get(position); holder.modelReady = false; holder.image = image; holder.thumbnail.setAspectRatio(image.getGifAspectRatio()); holder.gifProgress.setVisibility(View.GONE); DrawableRequestBuilder<String> thumbnailRequest = Glide.with(context).load(image.getStillUrl()); if (Util.isLowMemory(context)) { Glide.with(context) .load(image.getStillUrl()) .placeholder( new ColorDrawable( Util.getRandomElement(MaterialColor.values()).toConversationColor(context))) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(holder.thumbnail); holder.setModelReady(); } else { Glide.with(context) .load(image.getGifUrl()) .thumbnail(thumbnailRequest) .placeholder( new ColorDrawable( Util.getRandomElement(MaterialColor.values()).toConversationColor(context))) .diskCacheStrategy(DiskCacheStrategy.ALL) .listener(holder) .into(holder.thumbnail); } }
private void handleSmsRegistrationIntent(Intent intent) { markAsVerifying(true); String number = intent.getStringExtra("e164number"); MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); int registrationId = TextSecurePreferences.getLocalRegistrationId(this); if (registrationId == 0) { registrationId = KeyHelper.generateRegistrationId(false); TextSecurePreferences.setLocalRegistrationId(this, registrationId); } try { String password = Util.getSecret(18); String signalingKey = Util.getSecret(52); initializeChallengeListener(); setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number)); TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(this, number, password); accountManager.requestSmsVerificationCode(); setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number)); String challenge = waitForChallenge(); accountManager.verifyAccount(challenge, signalingKey, true, registrationId); handleCommonRegistration(masterSecret, accountManager, number); markAsVerified(number, password, signalingKey); setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number)); broadcastComplete(true); } catch (ExpectationFailedException efe) { Log.w("RegistrationService", efe); setState(new RegistrationState(RegistrationState.STATE_MULTI_REGISTERED, number)); broadcastComplete(false); } catch (UnsupportedOperationException uoe) { Log.w("RegistrationService", uoe); setState(new RegistrationState(RegistrationState.STATE_GCM_UNSUPPORTED, number)); broadcastComplete(false); } catch (AccountVerificationTimeoutException avte) { Log.w("RegistrationService", avte); setState(new RegistrationState(RegistrationState.STATE_TIMEOUT, number)); broadcastComplete(false); } catch (IOException e) { Log.w("RegistrationService", e); setState(new RegistrationState(RegistrationState.STATE_NETWORK_ERROR, number)); broadcastComplete(false); } finally { shutdownChallengeListener(); } }
private NotificationMmsMessageRecord getNotificationMmsMessageRecord(Cursor cursor) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID)); long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); Recipients recipients = getRecipientsFor(address); String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION)); String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID)); long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE)); long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY)); int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS)); int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT)); byte[] contentLocationBytes = null; byte[] transactionIdBytes = null; if (!TextUtils.isEmpty(contentLocation)) contentLocationBytes = org.thoughtcrime.securesms.util.Util.toIsoBytes(contentLocation); if (!TextUtils.isEmpty(transactionId)) transactionIdBytes = org.thoughtcrime.securesms.util.Util.toIsoBytes(transactionId); return new NotificationMmsMessageRecord( context, id, recipients, recipients.getPrimaryRecipient(), addressDeviceId, dateSent, dateReceived, receiptCount, threadId, contentLocationBytes, messageSize, expiry, status, transactionIdBytes, mailbox); }
public Cursor getFilteredConversationList(List<String> filter) { if (filter == null || filter.size() == 0) return null; List<Long> rawRecipientIds = DatabaseFactory.getAddressDatabase(context).getCanonicalAddressIds(filter); if (rawRecipientIds == null || rawRecipientIds.size() == 0) return null; SQLiteDatabase db = databaseHelper.getReadableDatabase(); List<List<Long>> partitionedRecipientIds = Util.partition(rawRecipientIds, 900); List<Cursor> cursors = new LinkedList<>(); for (List<Long> recipientIds : partitionedRecipientIds) { String selection = RECIPIENT_IDS + " = ?"; String[] selectionArgs = new String[recipientIds.size()]; for (int i = 0; i < recipientIds.size() - 1; i++) selection += (" OR " + RECIPIENT_IDS + " = ?"); int i = 0; for (long id : recipientIds) { selectionArgs[i++] = String.valueOf(id); } cursors.add(db.query(TABLE_NAME, null, selection, selectionArgs, null, null, DATE + " DESC")); } Cursor cursor = cursors.size() > 1 ? new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) : cursors.get(0); setNotifyConverationListListeners(cursor); return cursor; }
public static MasterSecret changeMasterSecretPassphrase( Context context, MasterSecret masterSecret, String newPassphrase) { try { byte[] combinedSecrets = Util.combine( masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded()); byte[] encryptionSalt = generateSalt(); int iterations = generateIterationCount(newPassphrase, encryptionSalt); byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, combinedSecrets, newPassphrase); byte[] macSalt = generateSalt(); byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, newPassphrase); save(context, "encryption_salt", encryptionSalt); save(context, "mac_salt", macSalt); save(context, "passphrase_iterations", iterations); save(context, "master_secret", encryptedAndMacdMasterSecret); save(context, "passphrase_initialized", true); return masterSecret; } catch (GeneralSecurityException gse) { throw new AssertionError(gse); } }
public static MasterSecret generateMasterSecret(Context context, String passphrase) { try { byte[] encryptionSecret = generateEncryptionSecret(); byte[] macSecret = generateMacSecret(); byte[] masterSecret = Util.combine(encryptionSecret, macSecret); byte[] encryptionSalt = generateSalt(); int iterations = generateIterationCount(passphrase, encryptionSalt); byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, masterSecret, passphrase); byte[] macSalt = generateSalt(); byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, passphrase); save(context, "encryption_salt", encryptionSalt); save(context, "mac_salt", macSalt); save(context, "passphrase_iterations", iterations); save(context, "master_secret", encryptedAndMacdMasterSecret); save(context, "passphrase_initialized", true); return new MasterSecret( new SecretKeySpec(encryptionSecret, "AES"), new SecretKeySpec(macSecret, "HmacSHA1")); } catch (GeneralSecurityException e) { Log.w("keyutil", e); return null; } }
private boolean isRelevant(Context context, Intent intent) { SmsMessage message = getSmsMessageFromIntent(intent); String messageBody = getSmsMessageBodyFromIntent(intent); if (message == null && messageBody == null) return false; if (isExemption(message, messageBody)) return false; if (!ApplicationMigrationService.isDatabaseImported(context)) return false; if (isChallenge(context, messageBody)) return false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && SMS_RECEIVED_ACTION.equals(intent.getAction()) && Util.isDefaultSmsProvider(context)) { return false; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && TextSecurePreferences.isInterceptAllSmsEnabled(context)) { return true; } return false; }
protected static byte[] parseResponse(InputStream is) throws IOException { InputStream in = new BufferedInputStream(is); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Util.copy(in, baos); Log.w(TAG, "Received full server response, " + baos.size() + " bytes"); return baos.toByteArray(); }
public void markIncomingNotificationReceived(long threadId) { notifyConversationListeners(threadId); DatabaseFactory.getThreadDatabase(context).update(threadId); if (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context)) { DatabaseFactory.getThreadDatabase(context).setUnread(threadId); } jobManager.add(new TrimThreadJob(context, threadId)); }
private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException, MmsException { if (retrieved.getGroupId() != null) { Recipients groupRecipients = RecipientFactory.getRecipientsFromString(context, retrieved.getGroupId(), true); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients); } String localNumber; Set<String> group = new HashSet<>(); if (retrieved.getAddresses().getFrom() == null) { throw new MmsException("FROM value in PduHeaders did not exist."); } group.add(retrieved.getAddresses().getFrom()); if (TextSecurePreferences.isPushRegistered(context)) { localNumber = TextSecurePreferences.getLocalNumber(context); } else { localNumber = ServiceUtil.getTelephonyManager(context).getLine1Number(); } for (String cc : retrieved.getAddresses().getCc()) { PhoneNumberUtil.MatchType match; if (localNumber == null) match = PhoneNumberUtil.MatchType.NO_MATCH; else match = PhoneNumberUtil.getInstance().isNumberMatch(localNumber, cc); if (match == PhoneNumberUtil.MatchType.NO_MATCH || match == PhoneNumberUtil.MatchType.NOT_A_NUMBER) { group.add(cc); } } if (retrieved.getAddresses().getTo().size() > 1) { for (String to : retrieved.getAddresses().getTo()) { PhoneNumberUtil.MatchType match; if (localNumber == null) match = PhoneNumberUtil.MatchType.NO_MATCH; else match = PhoneNumberUtil.getInstance().isNumberMatch(localNumber, to); if (match == PhoneNumberUtil.MatchType.NO_MATCH || match == PhoneNumberUtil.MatchType.NOT_A_NUMBER) { group.add(to); } } } String recipientsList = Util.join(group, ","); Recipients recipients = RecipientFactory.getRecipientsFromString(context, recipientsList, false); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); }
private long getThreadIdFor(@NonNull NotificationInd notification) { String fromString = notification.getFrom() != null && notification.getFrom().getTextString() != null ? Util.toIsoString(notification.getFrom().getTextString()) : ""; Recipients recipients = RecipientFactory.getRecipientsFromString(context, fromString, false); if (recipients.isEmpty()) recipients = RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(), false); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); }
private boolean isLocalNumber(Recipient recipient) { try { String localNumber = TextSecurePreferences.getLocalNumber(context); String e164Number = Util.canonicalizeNumber(context, recipient.getNumber()); return e164Number != null && e164Number.equals(localNumber); } catch (InvalidNumberException e) { Log.w(TAG, e); return false; } }
public GroupDescription(@NonNull Context context, @Nullable GroupContext groupContext) { this.context = context.getApplicationContext(); this.groupContext = groupContext; if (groupContext == null || groupContext.getMembersList().isEmpty()) { this.members = null; } else { this.members = RecipientFactory.getRecipientsFromString( context, Util.join(groupContext.getMembersList(), ", "), true); } }
public File getFile(boolean forMms) throws ExecutionException, InterruptedException { synchronized (this) { while (!modelReady) { Util.wait(this, 0); } } return Glide.with(context) .load(forMms ? image.getGifMmsUrl() : image.getGifUrl()) .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) .get(); }
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); long threadId = getThreadIdFor(notification); PduHeaders headers = notification.getPduHeaders(); ContentValues contentValues = new ContentValues(); ContentValuesBuilder contentBuilder = new ContentValuesBuilder(contentValues); Log.w(TAG, "Message received type: " + headers.getOctet(PduHeaders.MESSAGE_TYPE)); contentBuilder.add(CONTENT_LOCATION, headers.getTextString(PduHeaders.CONTENT_LOCATION)); contentBuilder.add(DATE_SENT, headers.getLongInteger(PduHeaders.DATE) * 1000L); contentBuilder.add(EXPIRY, headers.getLongInteger(PduHeaders.EXPIRY)); contentBuilder.add(MESSAGE_SIZE, headers.getLongInteger(PduHeaders.MESSAGE_SIZE)); contentBuilder.add(TRANSACTION_ID, headers.getTextString(PduHeaders.TRANSACTION_ID)); contentBuilder.add(MESSAGE_TYPE, headers.getOctet(PduHeaders.MESSAGE_TYPE)); if (headers.getEncodedStringValue(PduHeaders.FROM) != null) { contentBuilder.add(ADDRESS, headers.getEncodedStringValue(PduHeaders.FROM).getTextString()); } else { contentBuilder.add(ADDRESS, null); } contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE); contentValues.put(THREAD_ID, threadId); contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED); contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp()); contentValues.put(READ, Util.isDefaultSmsProvider(context) ? 0 : 1); if (!contentValues.containsKey(DATE_SENT)) contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)); long messageId = db.insert(TABLE_NAME, null, contentValues); addressDatabase.insertAddressesForId( messageId, MmsAddresses.forFrom(Util.toIsoString(notification.getFrom().getTextString()))); return new Pair<>(messageId, threadId); }
public static MasterSecret getMasterSecret(Context context, String passphrase) throws InvalidPassphraseException { try { byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret"); byte[] macSalt = retrieve(context, "mac_salt"); int iterations = retrieve(context, "passphrase_iterations", 100); byte[] encryptedMasterSecret = verifyMac(macSalt, iterations, encryptedAndMacdMasterSecret, passphrase); byte[] encryptionSalt = retrieve(context, "encryption_salt"); byte[] combinedSecrets = decryptWithPassphrase(encryptionSalt, iterations, encryptedMasterSecret, passphrase); byte[] encryptionSecret = Util.split(combinedSecrets, 16, 20)[0]; byte[] macSecret = Util.split(combinedSecrets, 16, 20)[1]; return new MasterSecret( new SecretKeySpec(encryptionSecret, "AES"), new SecretKeySpec(macSecret, "HmacSHA1")); } catch (GeneralSecurityException e) { Log.w("keyutil", e); return null; // XXX } catch (IOException e) { Log.w("keyutil", e); return null; // XXX } }
private Cursor rawQuery(@NonNull String where, @Nullable String[] arguments) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); return database.rawQuery( "SELECT " + Util.join(MMS_PROJECTION, ",") + " FROM " + MmsDatabase.TABLE_NAME + " LEFT OUTER JOIN " + AttachmentDatabase.TABLE_NAME + " ON (" + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " = " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + ")" + " WHERE " + where, arguments); }
protected Pair<Long, Long> insertMessageInbox(IncomingTextMessage message, long type) { if (message.isJoined()) { type = (type & (Types.TOTAL_MASK - Types.BASE_TYPE_MASK)) | Types.JOINED_TYPE; } else if (message.isPreKeyBundle()) { type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT; } else if (message.isSecureMessage()) { type |= Types.SECURE_MESSAGE_BIT; } else if (message.isGroup()) { type |= Types.SECURE_MESSAGE_BIT; if (((IncomingGroupMessage) message).isUpdate()) type |= Types.GROUP_UPDATE_BIT; else if (((IncomingGroupMessage) message).isQuit()) type |= Types.GROUP_QUIT_BIT; } else if (message.isEndSession()) { type |= Types.SECURE_MESSAGE_BIT; type |= Types.END_SESSION_BIT; } if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT; Recipients recipients; if (message.getSender() != null) { recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), true); } else { Log.w(TAG, "Sender is null, returning unknown recipient"); recipients = RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(), false); } Recipients groupRecipients; if (message.getGroupId() == null) { groupRecipients = null; } else { groupRecipients = RecipientFactory.getRecipientsFromString(context, message.getGroupId(), true); } boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) || message.isSecureMessage() || message.isPreKeyBundle(); long threadId; if (groupRecipients == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients); ContentValues values = new ContentValues(6); values.put(ADDRESS, message.getSender()); values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId()); values.put(DATE_RECEIVED, System.currentTimeMillis()); values.put(DATE_SENT, message.getSentTimestampMillis()); values.put(PROTOCOL, message.getProtocol()); values.put(READ, unread ? 0 : 1); if (!TextUtils.isEmpty(message.getPseudoSubject())) values.put(SUBJECT, message.getPseudoSubject()); values.put(REPLY_PATH_PRESENT, message.isReplyPathPresent()); values.put(SERVICE_CENTER, message.getServiceCenterAddress()); values.put(BODY, message.getMessageBody()); values.put(TYPE, type); values.put(THREAD_ID, threadId); SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, null, values); if (unread) { DatabaseFactory.getThreadDatabase(context).setUnread(threadId); } DatabaseFactory.getThreadDatabase(context).update(threadId, true); notifyConversationListeners(threadId); jobManager.add(new TrimThreadJob(context, threadId)); return new Pair<>(messageId, threadId); }
/** * List adapter to display all contacts and their related information * * @author Jake McGinty */ public class ContactSelectionListAdapter extends CursorAdapter implements StickyListHeadersAdapter { private static final String TAG = "ContactListAdapter"; private static final ExecutorService photoResolver = Util.newSingleThreadedLifoExecutor(); private static final int STYLE_ATTRIBUTES[] = new int[] { R.attr.contact_selection_push_user, R.attr.contact_selection_lay_user, R.attr.contact_selection_label_text }; private int TYPE_COLUMN = -1; private int NAME_COLUMN = -1; private int NUMBER_COLUMN = -1; private int NUMBER_TYPE_COLUMN = -1; private int ID_COLUMN = -1; private final Context context; private final boolean multiSelect; private final LayoutInflater li; private final TypedArray drawables; private final Bitmap defaultPhoto; private final Bitmap defaultCroppedPhoto; private final int scaledPhotoSize; private final HashMap<Long, ContactAccessor.ContactData> selectedContacts = new HashMap<Long, ContactAccessor.ContactData>(); public ContactSelectionListAdapter(Context context, Cursor cursor, boolean multiSelect) { super(context, cursor, 0); this.context = context; this.li = LayoutInflater.from(context); this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES); this.multiSelect = multiSelect; this.defaultPhoto = ContactPhotoFactory.getDefaultContactPhoto(context); this.scaledPhotoSize = context.getResources().getDimensionPixelSize(R.dimen.contact_selection_photo_size); this.defaultCroppedPhoto = BitmapUtil.getScaledCircleCroppedBitmap(defaultPhoto, scaledPhotoSize); } public static class ViewHolder { public CheckBox checkBox; public TextView name; public TextView number; public ImageView contactPhoto; public int position; } public static class DataHolder { public int type; public String name; public String number; public int numberType; public long id; } public static class HeaderViewHolder { TextView text; } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final View v = li.inflate(R.layout.push_contact_selection_list_item, parent, false); final ViewHolder holder = new ViewHolder(); if (v != null) { holder.name = (TextView) v.findViewById(R.id.name); holder.number = (TextView) v.findViewById(R.id.number); holder.checkBox = (CheckBox) v.findViewById(R.id.check_box); holder.contactPhoto = (ImageView) v.findViewById(R.id.contact_photo_image); if (!multiSelect) holder.checkBox.setVisibility(View.GONE); v.setTag(R.id.holder_tag, holder); v.setTag(R.id.contact_info_tag, new DataHolder()); } return v; } @Override public void bindView(View view, Context context, Cursor cursor) { final DataHolder contactData = (DataHolder) view.getTag(R.id.contact_info_tag); final ViewHolder holder = (ViewHolder) view.getTag(R.id.holder_tag); if (holder == null) { Log.w(TAG, "ViewHolder was null. This should not happen."); return; } if (contactData == null) { Log.w(TAG, "DataHolder was null. This should not happen."); return; } if (ID_COLUMN < 0) { populateColumnIndices(cursor); } contactData.type = cursor.getInt(TYPE_COLUMN); contactData.name = cursor.getString(NAME_COLUMN); contactData.number = cursor.getString(NUMBER_COLUMN); contactData.numberType = cursor.getInt(NUMBER_TYPE_COLUMN); contactData.id = cursor.getLong(ID_COLUMN); if (contactData.type != ContactsDatabase.PUSH_TYPE) { holder.name.setTextColor(drawables.getColor(1, 0xff000000)); holder.number.setTextColor(drawables.getColor(1, 0xff000000)); } else { holder.name.setTextColor(drawables.getColor(0, 0xa0000000)); holder.number.setTextColor(drawables.getColor(0, 0xa0000000)); } if (selectedContacts.containsKey(contactData.id)) { holder.checkBox.setChecked(true); } else { holder.checkBox.setChecked(false); } holder.name.setText(contactData.name); if (contactData.number == null || contactData.number.isEmpty()) { holder.name.setEnabled(false); holder.number.setText(""); } else if (contactData.type == ContactsDatabase.PUSH_TYPE) { holder.number.setText(contactData.number); } else { final CharSequence label = ContactsContract.CommonDataKinds.Phone.getTypeLabel( context.getResources(), contactData.numberType, ""); final CharSequence numberWithLabel = contactData.number + " " + label; final Spannable numberLabelSpan = new SpannableString(numberWithLabel); numberLabelSpan.setSpan( new ForegroundColorSpan(drawables.getColor(2, 0xff444444)), contactData.number.length(), numberWithLabel.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); holder.number.setText(numberLabelSpan); } holder.contactPhoto.setImageBitmap(defaultCroppedPhoto); loadBitmap(contactData.number, holder.contactPhoto); } @Override public View getHeaderView(int i, View convertView, ViewGroup viewGroup) { final Cursor c = getCursor(); final HeaderViewHolder holder; if (convertView == null) { holder = new HeaderViewHolder(); convertView = li.inflate(R.layout.push_contact_selection_list_header, viewGroup, false); holder.text = (TextView) convertView.findViewById(R.id.text); convertView.setTag(holder); } else { holder = (HeaderViewHolder) convertView.getTag(); } c.moveToPosition(i); final int type = c.getInt(c.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN)); final int headerTextRes; switch (type) { case 1: headerTextRes = R.string.contact_selection_list__header_textsecure_users; break; default: headerTextRes = R.string.contact_selection_list__header_other; break; } holder.text.setText(headerTextRes); return convertView; } @Override public long getHeaderId(int i) { final Cursor c = getCursor(); c.moveToPosition(i); return c.getInt(c.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN)); } public boolean cancelPotentialWork(String number, ImageView imageView) { final TaggedFutureTask<?> bitmapWorkerTask = AsyncDrawable.getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final Object tag = bitmapWorkerTask.getTag(); if (tag != null && !tag.equals(number)) { bitmapWorkerTask.cancel(true); } else { return false; } } return true; } public void loadBitmap(String number, ImageView imageView) { if (cancelPotentialWork(number, imageView)) { final BitmapWorkerRunnable runnable = new BitmapWorkerRunnable(context, imageView, defaultPhoto, number, scaledPhotoSize); final TaggedFutureTask<?> task = new TaggedFutureTask<Void>(runnable, null, number); final AsyncDrawable asyncDrawable = new AsyncDrawable(context.getResources(), defaultCroppedPhoto, task); imageView.setImageDrawable(asyncDrawable); if (!task.isCancelled()) photoResolver.execute(new FutureTask<Void>(task, null)); } } public Map<Long, ContactAccessor.ContactData> getSelectedContacts() { return selectedContacts; } private void populateColumnIndices(final Cursor cursor) { this.TYPE_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN); this.NAME_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN); this.NUMBER_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN); this.NUMBER_TYPE_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN); this.ID_COLUMN = cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN); } }
public class RecipientProvider { private static final String TAG = RecipientProvider.class.getSimpleName(); private static final RecipientCache recipientCache = new RecipientCache(); private static final RecipientsCache recipientsCache = new RecipientsCache(); private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor(); private static final String[] CALLER_ID_PROJECTION = new String[] { PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY, PhoneLookup._ID, PhoneLookup.NUMBER }; Recipient getRecipient(Context context, long recipientId, boolean asynchronous) { Recipient cachedRecipient = recipientCache.get(recipientId); if (cachedRecipient != null && !cachedRecipient.isStale()) return cachedRecipient; String number = CanonicalAddressDatabase.getInstance(context).getAddressFromId(recipientId); if (asynchronous) { cachedRecipient = new Recipient( recipientId, number, cachedRecipient, getRecipientDetailsAsync(context, recipientId, number)); } else { cachedRecipient = new Recipient(recipientId, getRecipientDetailsSync(context, recipientId, number)); } recipientCache.set(recipientId, cachedRecipient); return cachedRecipient; } Recipients getRecipients(Context context, long[] recipientIds, boolean asynchronous) { Recipients cachedRecipients = recipientsCache.get(new RecipientIds(recipientIds)); if (cachedRecipients != null && !cachedRecipients.isStale()) return cachedRecipients; List<Recipient> recipientList = new LinkedList<>(); for (long recipientId : recipientIds) { recipientList.add(getRecipient(context, recipientId, asynchronous)); } if (asynchronous) cachedRecipients = new Recipients( recipientList, cachedRecipients, getRecipientsPreferencesAsync(context, recipientIds)); else cachedRecipients = new Recipients(recipientList, getRecipientsPreferencesSync(context, recipientIds)); recipientsCache.set(new RecipientIds(recipientIds), cachedRecipients); return cachedRecipients; } void clearCache() { recipientCache.reset(); recipientsCache.reset(); } private @NonNull ListenableFutureTask<RecipientDetails> getRecipientDetailsAsync( final Context context, final long recipientId, final String number) { Callable<RecipientDetails> task = new Callable<RecipientDetails>() { @Override public RecipientDetails call() throws Exception { return getRecipientDetailsSync(context, recipientId, number); } }; ListenableFutureTask<RecipientDetails> future = new ListenableFutureTask<>(task); asyncRecipientResolver.submit(future); return future; } private @NonNull RecipientDetails getRecipientDetailsSync( Context context, long recipientId, String number) { if (GroupUtil.isEncodedGroup(number)) return getGroupRecipientDetails(context, number); else return getIndividualRecipientDetails(context, recipientId, number); } private @NonNull RecipientDetails getIndividualRecipientDetails( Context context, long recipientId, String number) { Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context) .getRecipientsPreferences(new long[] {recipientId}); MaterialColor color = preferences.isPresent() ? preferences.get().getColor() : null; Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { Uri contactUri = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1)); String name = cursor.getString(3).equals(cursor.getString(0)) ? null : cursor.getString(0); ContactPhoto contactPhoto = ContactPhotoFactory.getContactPhoto( context, Uri.withAppendedPath(Contacts.CONTENT_URI, cursor.getLong(2) + ""), name); return new RecipientDetails( cursor.getString(0), cursor.getString(3), contactUri, contactPhoto, color); } } finally { if (cursor != null) cursor.close(); } return new RecipientDetails( null, number, null, ContactPhotoFactory.getDefaultContactPhoto(null), color); } private @NonNull RecipientDetails getGroupRecipientDetails(Context context, String groupId) { try { GroupDatabase.GroupRecord record = DatabaseFactory.getGroupDatabase(context).getGroup(GroupUtil.getDecodedId(groupId)); if (record != null) { ContactPhoto contactPhoto = ContactPhotoFactory.getGroupContactPhoto(record.getAvatar()); return new RecipientDetails(record.getTitle(), groupId, null, contactPhoto, null); } return new RecipientDetails( null, groupId, null, ContactPhotoFactory.getDefaultGroupPhoto(), null); } catch (IOException e) { Log.w("RecipientProvider", e); return new RecipientDetails( null, groupId, null, ContactPhotoFactory.getDefaultGroupPhoto(), null); } } private @Nullable RecipientsPreferences getRecipientsPreferencesSync( Context context, long[] recipientIds) { return DatabaseFactory.getRecipientPreferenceDatabase(context) .getRecipientsPreferences(recipientIds) .orNull(); } private ListenableFutureTask<RecipientsPreferences> getRecipientsPreferencesAsync( final Context context, final long[] recipientIds) { ListenableFutureTask<RecipientsPreferences> task = new ListenableFutureTask<>( new Callable<RecipientsPreferences>() { @Override public RecipientsPreferences call() throws Exception { return getRecipientsPreferencesSync(context, recipientIds); } }); asyncRecipientResolver.execute(task); return task; } public static class RecipientDetails { @Nullable public final String name; @NonNull public final String number; @NonNull public final ContactPhoto avatar; @Nullable public final Uri contactUri; @Nullable public final MaterialColor color; public RecipientDetails( @Nullable String name, @NonNull String number, @Nullable Uri contactUri, @NonNull ContactPhoto avatar, @Nullable MaterialColor color) { this.name = name; this.number = number; this.avatar = avatar; this.contactUri = contactUri; this.color = color; } } private static class RecipientIds { private final long[] ids; private RecipientIds(long[] ids) { this.ids = ids; } public boolean equals(Object other) { if (other == null || !(other instanceof RecipientIds)) return false; return Arrays.equals(this.ids, ((RecipientIds) other).ids); } public int hashCode() { return Arrays.hashCode(ids); } } private static class RecipientCache { private final Map<Long, Recipient> cache = new LRUCache<>(1000); public synchronized Recipient get(long recipientId) { return cache.get(recipientId); } public synchronized void set(long recipientId, Recipient recipient) { cache.put(recipientId, recipient); } public synchronized void reset() { for (Recipient recipient : cache.values()) { recipient.setStale(); } } } private static class RecipientsCache { private final Map<RecipientIds, Recipients> cache = new LRUCache<>(1000); public synchronized Recipients get(RecipientIds ids) { return cache.get(ids); } public synchronized void set(RecipientIds ids, Recipients recipients) { cache.put(ids, recipients); } public synchronized void reset() { for (Recipients recipients : cache.values()) { recipients.setStale(); } } } }