下面列出了怎么用androidx.annotation.WorkerThread的API类实例代码及写法,或者点击链接到github查看源代码。
@WorkerThread
private Optional<String> requestAndReceiveChallengeBlocking() {
EventBus eventBus = EventBus.getDefault();
eventBus.register(this);
try {
accountManager.requestPushChallenge(fcmToken, e164number);
latch.await(timeoutMs, TimeUnit.MILLISECONDS);
return Optional.fromNullable(challenge.get());
} catch (InterruptedException | IOException e) {
Log.w(TAG, "Error getting push challenge", e);
return Optional.absent();
} finally {
eventBus.unregister(this);
}
}
/**
* Should only be called by {@link org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob}.
*/
@WorkerThread
public static synchronized void onMigrateToRegistrationLockV2(@NonNull Context context, @NonNull String pin)
throws IOException, UnauthenticatedResponseException
{
KbsValues kbsValues = SignalStore.kbsValues();
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
pinChangeSession.enableRegistrationLock(masterKey);
kbsValues.setKbsMasterKey(kbsData, pin);
TextSecurePreferences.clearRegistrationLockV1(context);
updateState(buildInferredStateFromOtherFields());
}
@WorkerThread
public static Bitmap createScaledBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {
if (bitmap.getWidth() <= maxWidth && bitmap.getHeight() <= maxHeight) {
return bitmap;
}
if (maxWidth <= 0 || maxHeight <= 0) {
return bitmap;
}
int newWidth = maxWidth;
int newHeight = maxHeight;
float widthRatio = bitmap.getWidth() / (float) maxWidth;
float heightRatio = bitmap.getHeight() / (float) maxHeight;
if (widthRatio > heightRatio) {
newHeight = (int) (bitmap.getHeight() / widthRatio);
} else {
newWidth = (int) (bitmap.getWidth() / heightRatio);
}
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
}
@WorkerThread
public Cursor querySignalContacts(@NonNull String query, boolean includeSelf) {
Cursor cursor = TextUtils.isEmpty(query) ? recipientDatabase.getSignalContacts(includeSelf)
: recipientDatabase.querySignalContacts(query, includeSelf);
if (includeSelf && noteToSelfTitle.toLowerCase().contains(query.toLowerCase())) {
Recipient self = Recipient.self();
boolean nameMatch = self.getDisplayName(context).toLowerCase().contains(query.toLowerCase());
boolean numberMatch = self.getE164().isPresent() && self.requireE164().contains(query);
boolean shouldAdd = !nameMatch && !numberMatch;
if (shouldAdd) {
MatrixCursor selfCursor = new MatrixCursor(RecipientDatabase.SEARCH_PROJECTION_NAMES);
selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, null, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), noteToSelfTitle });
cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor });
}
}
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
}
/**
* Called when registration lock is enabled in the settings using the old UI (i.e. no mention of
* Signal PINs).
*/
@WorkerThread
public static synchronized void onEnableLegacyRegistrationLockPreference(@NonNull Context context, @NonNull String pin)
throws IOException, UnauthenticatedResponseException
{
Log.i(TAG, "onCompleteRegistrationLockV1Reminder()");
assertState(State.NO_REGISTRATION_LOCK);
KbsValues kbsValues = SignalStore.kbsValues();
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
pinChangeSession.enableRegistrationLock(masterKey);
kbsValues.setKbsMasterKey(kbsData, pin);
kbsValues.setV2RegistrationLockEnabled(true);
TextSecurePreferences.clearRegistrationLockV1(context);
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
updateState(buildInferredStateFromOtherFields());
}
@WorkerThread
private synchronized @NonNull Uri writeBlobSpecToDisk(@NonNull Context context, @NonNull BlobSpec blobSpec)
throws IOException
{
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<IOException> exception = new AtomicReference<>(null);
Uri uri = writeBlobSpecToDiskAsync(context, blobSpec, latch::countDown, exception::set);
try {
latch.await();
} catch (InterruptedException e) {
throw new IOException(e);
}
if (exception.get() != null) {
throw exception.get();
}
return uri;
}
@WorkerThread
private void unbindFromSurface() {
mIsBound = false;
mPreviewStreamSize = null;
mCaptureSize = null;
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(null);
} else if (mPreview.getOutputClass() == SurfaceTexture.class) {
mCamera.setPreviewTexture(null);
} else {
throw new RuntimeException("Unknown CameraPreview output class.");
}
} catch (IOException e) {
LOG.e("unbindFromSurface", "Could not release surface", e);
}
}
@WorkerThread
public static boolean isAutoDownloadPermitted(@NonNull Context context, @Nullable DatabaseAttachment attachment) {
if (attachment == null) {
Log.w(TAG, "attachment was null, returning vacuous true");
return true;
}
if (isFromUnknownContact(context, attachment)) {
return false;
}
Set<String> allowedTypes = getAllowedAutoDownloadTypes(context);
String contentType = attachment.getContentType();
if (attachment.isVoiceNote() ||
(MediaUtil.isAudio(attachment) && TextUtils.isEmpty(attachment.getFileName())) ||
MediaUtil.isLongTextType(attachment.getContentType()) ||
attachment.isSticker())
{
return true;
} else if (isNonDocumentType(contentType)) {
return allowedTypes.contains(MediaUtil.getDiscreteMimeType(contentType));
} else {
return allowedTypes.contains("documents");
}
}
/**
* Retrieves the current FCM token. If one isn't available, it'll be generated.
*/
@WorkerThread
public static Optional<String> getToken() {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<String> token = new AtomicReference<>(null);
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
if (task.isSuccessful() && task.getResult() != null && !TextUtils.isEmpty(task.getResult().getToken())) {
token.set(task.getResult().getToken());
} else {
Log.w(TAG, "Failed to get the token.", task.getException());
}
latch.countDown();
});
try {
latch.await();
} catch (InterruptedException e) {
Log.w(TAG, "Was interrupted while waiting for the token.");
}
return Optional.fromNullable(token.get());
}
@WorkerThread
@NonNull GroupManager.GroupActionResult updateGroupTitleAndAvatar(@Nullable String title, @Nullable byte[] avatarBytes, boolean avatarChanged)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
try {
GroupChange.Actions.Builder change = groupOperations.createModifyGroupTitleAndMembershipChange(Optional.fromNullable(title), Collections.emptySet(), Collections.emptySet());
if (avatarChanged) {
String cdnKey = avatarBytes != null ? groupsV2Api.uploadAvatar(avatarBytes, groupSecretParams, authorization.getAuthorizationForToday(selfUuid, groupSecretParams))
: "";
change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder()
.setAvatar(cdnKey));
}
GroupManager.GroupActionResult groupActionResult = commitChangeWithConflictResolution(change);
if (avatarChanged) {
AvatarHelper.setAvatar(context, Recipient.externalGroup(context, groupId).getId(), avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
groupDatabase.onAvatarUpdated(groupId, avatarBytes != null);
}
return groupActionResult;
} catch (VerificationFailedException e) {
throw new GroupChangeFailedException(e);
}
}
/**
* Retrieves the next job that is eligible for execution. To be 'eligible' means that the job:
* - Has no dependencies
* - Has no unmet constraints
*
* This method will block until a job is available.
* When the job returned from this method has been run, you must call {@link #onJobFinished(Job)}.
*/
@WorkerThread
synchronized @NonNull Job pullNextEligibleJobForExecution() {
try {
Job job;
while ((job = getNextEligibleJobForExecution()) == null) {
if (runningJobs.isEmpty()) {
debouncer.publish(callback::onEmpty);
}
wait();
}
jobStorage.updateJobRunningState(job.getId(), true);
runningJobs.put(job.getId(), job);
jobTracker.onStateChange(job, JobTracker.JobState.RUNNING);
return job;
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted.");
throw new AssertionError(e);
}
}
@WorkerThread
private static LinkedHashMap<Media, Media> transformMedia(@NonNull Context context,
@NonNull List<Media> currentMedia,
@NonNull Map<Media, MediaTransform> modelsToTransform)
{
LinkedHashMap<Media, Media> updatedMedia = new LinkedHashMap<>(currentMedia.size());
for (Media media : currentMedia) {
MediaTransform transformer = modelsToTransform.get(media);
if (transformer != null) {
updatedMedia.put(media, transformer.transform(context, media));
} else {
updatedMedia.put(media, media);
}
}
return updatedMedia;
}
/**
* Deletes index data for the specified cache.
*
* <p>This method may be slow and shouldn't normally be called on the main thread.
*
* @param databaseProvider Provides the database in which the index is stored.
* @param uid The cache UID.
* @throws DatabaseIOException If an error occurs deleting the index data.
*/
@WorkerThread
public static void delete(DatabaseProvider databaseProvider, long uid)
throws DatabaseIOException {
String hexUid = Long.toHexString(uid);
try {
String tableName = getTableName(hexUid);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransactionNonExclusive();
try {
VersionTable.removeVersion(
writableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA, hexUid);
dropTable(writableDatabase, tableName);
writableDatabase.setTransactionSuccessful();
} finally {
writableDatabase.endTransaction();
}
} catch (SQLException e) {
throw new DatabaseIOException(e);
}
}
@WorkerThread
private @Nullable Job getNextEligibleJobForExecution() {
List<JobSpec> jobSpecs = jobStorage.getPendingJobsWithNoDependenciesInCreatedOrder(System.currentTimeMillis());
for (JobSpec jobSpec : jobSpecs) {
List<ConstraintSpec> constraintSpecs = jobStorage.getConstraintSpecs(jobSpec.getId());
List<Constraint> constraints = Stream.of(constraintSpecs)
.map(ConstraintSpec::getFactoryKey)
.map(constraintInstantiator::instantiate)
.toList();
if (Stream.of(constraints).allMatch(Constraint::isMet)) {
return createJob(jobSpec, constraintSpecs);
}
}
return null;
}
@WorkerThread
private PushProcessMessageJob(@NonNull MessageState messageState,
@Nullable SignalServiceContent content,
@Nullable ExceptionMetadata exceptionMetadata,
long pushMessageId,
long smsMessageId,
long timestamp)
{
this(createParameters(content, exceptionMetadata),
messageState,
content,
exceptionMetadata,
pushMessageId,
smsMessageId,
timestamp);
}
@WorkerThread
synchronized void onRetry(@NonNull Job job) {
int nextRunAttempt = job.getRunAttempt() + 1;
long nextRunAttemptTime = calculateNextRunAttemptTime(System.currentTimeMillis(), nextRunAttempt, job.getParameters().getMaxBackoff());
String serializedData = dataSerializer.serialize(job.serialize());
jobStorage.updateJobAfterRetry(job.getId(), false, nextRunAttempt, nextRunAttemptTime, serializedData);
jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
List<Constraint> constraints = Stream.of(jobStorage.getConstraintSpecs(job.getId()))
.map(ConstraintSpec::getFactoryKey)
.map(constraintInstantiator::instantiate)
.toList();
long delay = Math.max(0, nextRunAttemptTime - System.currentTimeMillis());
Log.i(TAG, JobLogger.format(job, "Scheduling a retry in " + delay + " ms."));
scheduler.schedule(delay, constraints);
notifyAll();
}
@WorkerThread
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Recipient recipient) {
try {
if (!recipient.hasServiceIdentifier()) {
throw new AssertionError();
}
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
Set<String> attachmentUploadIds = enqueueCompressingAndUploadAttachmentsChains(jobManager, message);
jobManager.add(new PushMediaSendJob(messageId, recipient), attachmentUploadIds, recipient.getId().toQueueKey());
} catch (NoSuchMessageException | MmsException e) {
Log.w(TAG, "Failed to enqueue message.", e);
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
@WorkerThread
@Override
void onStart() {
if (isCameraAvailable()) {
LOG.w("onStart:", "Camera not available. Should not happen.");
onStop(); // Should not happen.
}
if (collectCameraId()) {
createCamera();
if (shouldBindToSurface()) bindToSurface();
if (shouldStartPreview()) startPreview("onStart");
LOG.i("onStart:", "Ended");
} else {
LOG.e("onStart:", "No camera available for facing", mFacing);
throw new CameraException(CameraException.REASON_NO_CAMERA);
}
}
/**
* Deletes the specified attachment. If its the only attachment for its linked message, the entire
* message is deleted.
*/
@WorkerThread
public static void deleteAttachment(@NonNull Context context,
@NonNull DatabaseAttachment attachment)
{
AttachmentId attachmentId = attachment.getAttachmentId();
long mmsId = attachment.getMmsId();
int attachmentCount = DatabaseFactory.getAttachmentDatabase(context)
.getAttachmentsForMessage(mmsId)
.size();
if (attachmentCount <= 1) {
DatabaseFactory.getMmsDatabase(context).delete(mmsId);
} else {
DatabaseFactory.getAttachmentDatabase(context).deleteAttachment(attachmentId);
}
}
/**
* Invoked whenever a Signal PIN user disables registration lock.
*/
@WorkerThread
public static synchronized void onDisableRegistrationLockForUserWithPin() throws IOException {
Log.i(TAG, "onDisableRegistrationLockForUserWithPin()");
if (getState() == State.PIN_WITH_REGISTRATION_LOCK_DISABLED) {
Log.i(TAG, "Registration lock already disabled. Skipping.");
return;
}
assertState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
ApplicationDependencies.getKeyBackupService()
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
.disableRegistrationLock();
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
updateState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
}
/**
* Convenience method that attempts to start all the components
*
* @param context
*/
@WorkerThread
public synchronized void start(Context context) {
startGattClient(context);
startGattServer(context);
initializeScanner(context);
}
@WorkerThread
@Override
public @NonNull Media transform(@NonNull Context context, @NonNull Media media) {
return new Media(media.getUri(),
media.getMimeType(),
media.getDate(),
media.getWidth(),
media.getHeight(),
media.getSize(),
media.getDuration(),
media.getBucketId(),
media.getCaption(),
Optional.of(new AttachmentDatabase.TransformProperties(false, data.durationEdited, data.startTimeUs, data.endTimeUs)));
}
/**
* @return True if a conversation existed before we enabled message requests, otherwise false.
*/
@WorkerThread
public static boolean isPreMessageRequestThread(@NonNull Context context, long threadId) {
if (!FeatureFlags.messageRequests()) {
return true;
}
long beforeTime = SignalStore.getMessageRequestEnableTime();
return DatabaseFactory.getMmsSmsDatabase(context).getConversationCount(threadId, beforeTime) > 0;
}
@WorkerThread
private void insertJobChain(@NonNull List<List<Job>> chain) {
List<FullSpec> fullSpecs = new LinkedList<>();
List<String> dependsOn = Collections.emptyList();
for (List<Job> jobList : chain) {
for (Job job : jobList) {
fullSpecs.add(buildFullSpec(job, dependsOn));
}
dependsOn = Stream.of(jobList).map(Job::getId).toList();
}
jobStorage.insertJobs(fullSpecs);
}
/**
* See {@link #isMessageRequestAccepted(Context, long)}.
*/
@WorkerThread
public static boolean isMessageRequestAccepted(@NonNull Context context, @Nullable Recipient threadRecipient) {
if (!FeatureFlags.messageRequests() || threadRecipient == null) {
return true;
}
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient);
return isMessageRequestAccepted(context, threadId, threadRecipient);
}
/**
* Merges {@link DownloadRequest DownloadRequests} contained in a legacy action file into a {@link
* DefaultDownloadIndex}, deleting the action file if the merge is successful or if {@code
* deleteOnFailure} is {@code true}.
*
* <p>This method must not be called while the {@link DefaultDownloadIndex} is being used by a
* {@link DownloadManager}.
*
* <p>This method may be slow and shouldn't normally be called on the main thread.
*
* @param actionFilePath The action file path.
* @param downloadIdProvider A download ID provider, or {@code null}. If {@code null} then ID of
* each download will be its custom cache key if one is specified, or else its URL.
* @param downloadIndex The index into which the requests will be merged.
* @param deleteOnFailure Whether to delete the action file if the merge fails.
* @param addNewDownloadsAsCompleted Whether to add new downloads as completed.
* @throws IOException If an error occurs loading or merging the requests.
*/
@WorkerThread
@SuppressWarnings("deprecation")
public static void upgradeAndDelete(
File actionFilePath,
@Nullable DownloadIdProvider downloadIdProvider,
DefaultDownloadIndex downloadIndex,
boolean deleteOnFailure,
boolean addNewDownloadsAsCompleted)
throws IOException {
ActionFile actionFile = new ActionFile(actionFilePath);
if (actionFile.exists()) {
boolean success = false;
try {
long nowMs = System.currentTimeMillis();
for (DownloadRequest request : actionFile.load()) {
if (downloadIdProvider != null) {
request = request.copyWithId(downloadIdProvider.getId(request));
}
mergeRequest(request, downloadIndex, addNewDownloadsAsCompleted, nowMs);
}
success = true;
} finally {
if (success || deleteOnFailure) {
actionFile.delete();
}
}
}
}
/**
* Stores the index data to index file if there is a change.
*
* <p>This method may be slow and shouldn't normally be called on the main thread.
*
* @throws IOException If an error occurs storing the index data.
*/
@WorkerThread
public void store() throws IOException {
storage.storeIncremental(keyToContent);
// Make ids that were removed since the index was last stored eligible for re-use.
int removedIdCount = removedIds.size();
for (int i = 0; i < removedIdCount; i++) {
idToKey.remove(removedIds.keyAt(i));
}
removedIds.clear();
newIds.clear();
}
/**
* Helper function to access the database and update the video information in the database.
*
* @param video video entity
* @param category which fields to update
* @param value updated value
*/
@WorkerThread
public synchronized void updateDatabase(VideoEntity video, String category, String value) {
try {
mDb.beginTransaction();
switch (category) {
case VIDEO:
video.setVideoLocalStorageUrl(value);
break;
case BACKGROUND:
video.setVideoBgImageLocalStorageUrl(value);
break;
case CARD:
video.setVideoCardImageLocalStorageUrl(value);
break;
case STATUS:
video.setStatus(value);
break;
case RENTED:
video.setRented(true);
break;
}
mDb.videoDao().updateVideo(video);
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
}
@WorkerThread
private @Nullable Contact getContactFromSystemContacts(long contactId) {
Name name = getName(contactId);
if (name == null) {
Log.w(TAG, "Couldn't find a name associated with the provided contact ID.");
return null;
}
List<Phone> phoneNumbers = getPhoneNumbers(contactId);
AvatarInfo avatarInfo = getAvatarInfo(contactId, phoneNumbers);
Avatar avatar = avatarInfo != null ? new Avatar(avatarInfo.uri, avatarInfo.isProfile) : null;
return new Contact(name, null, phoneNumbers, getEmails(contactId), getPostalAddresses(contactId), avatar);
}
@WorkerThread
private static AlertDialog.Builder buildUnblockFor(@NonNull Context context,
@NonNull Recipient recipient,
@NonNull Runnable onUnblock)
{
recipient = recipient.resolve();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
Resources resources = context.getResources();
if (recipient.isGroup()) {
if (DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireGroupId())) {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_group_members_will_be_able_to_add_you);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
} else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_group_members_will_be_able_to_add_you);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
}
} else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_you_will_be_able_to_call_and_message_each_other);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
}
return builder;
}