Move all files to natural position.

This commit is contained in:
Alan Evans
2020-01-06 10:52:48 -05:00
parent 0df36047e7
commit 9ebe920195
3016 changed files with 6 additions and 36 deletions

View File

@@ -0,0 +1,80 @@
package org.thoughtcrime.securesms.service;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.NetworkErrorException;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
public class AccountAuthenticatorService extends Service {
private static AccountAuthenticatorImpl accountAuthenticator = null;
@Override
public IBinder onBind(Intent intent) {
if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
return getAuthenticator().getIBinder();
} else {
return null;
}
}
private synchronized AccountAuthenticatorImpl getAuthenticator() {
if (accountAuthenticator == null) {
accountAuthenticator = new AccountAuthenticatorImpl(this);
}
return accountAuthenticator;
}
private static class AccountAuthenticatorImpl extends AbstractAccountAuthenticator {
public AccountAuthenticatorImpl(Context context) {
super(context);
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException
{
return null;
}
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) {
return null;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType,
Bundle options) throws NetworkErrorException {
return null;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
throws NetworkErrorException {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType,
Bundle options) {
return null;
}
}
}

View File

@@ -0,0 +1,18 @@
package org.thoughtcrime.securesms.service;
public class AccountVerificationTimeoutException extends Exception {
public AccountVerificationTimeoutException() {
}
public AccountVerificationTimeoutException(String detailMessage) {
super(detailMessage);
}
public AccountVerificationTimeoutException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public AccountVerificationTimeoutException(Throwable throwable) {
super(throwable);
}
}

View File

@@ -0,0 +1,222 @@
package org.thoughtcrime.securesms.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import androidx.core.app.NotificationCompat;
import org.thoughtcrime.securesms.MainActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.SmsMigrator;
import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
// FIXME: This class is nuts.
public class ApplicationMigrationService extends Service
implements SmsMigrator.SmsMigrationProgressListener
{
private static final String TAG = ApplicationMigrationService.class.getSimpleName();
public static final String MIGRATE_DATABASE = "org.thoughtcrime.securesms.ApplicationMigration.MIGRATE_DATABSE";
public static final String COMPLETED_ACTION = "org.thoughtcrime.securesms.ApplicationMigrationService.COMPLETED";
private static final String PREFERENCES_NAME = "SecureSMS";
private static final String DATABASE_MIGRATED = "migrated";
private final BroadcastReceiver completedReceiver = new CompletedReceiver();
private final Binder binder = new ApplicationMigrationBinder();
private final Executor executor = Executors.newSingleThreadExecutor();
private WeakReference<Handler> handler = null;
private NotificationCompat.Builder notification = null;
private ImportState state = new ImportState(ImportState.STATE_IDLE, null);
@Override
public void onCreate() {
registerCompletedReceiver();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) return START_NOT_STICKY;
if (intent.getAction() != null && intent.getAction().equals(MIGRATE_DATABASE)) {
executor.execute(new ImportRunnable());
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
unregisterCompletedReceiver();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void setImportStateHandler(Handler handler) {
this.handler = new WeakReference<>(handler);
}
private void registerCompletedReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(COMPLETED_ACTION);
registerReceiver(completedReceiver, filter);
}
private void unregisterCompletedReceiver() {
unregisterReceiver(completedReceiver);
}
private void notifyImportComplete() {
Intent intent = new Intent();
intent.setAction(COMPLETED_ACTION);
sendOrderedBroadcast(intent, null);
}
@Override
public void progressUpdate(ProgressDescription progress) {
setState(new ImportState(ImportState.STATE_MIGRATING_IN_PROGRESS, progress));
}
public ImportState getState() {
return state;
}
private void setState(ImportState state) {
this.state = state;
if (this.handler != null) {
Handler handler = this.handler.get();
if (handler != null) {
handler.obtainMessage(state.state, state.progress).sendToTarget();
}
}
if (state.progress != null && state.progress.secondaryComplete == 0) {
updateBackgroundNotification(state.progress.primaryTotal, state.progress.primaryComplete);
}
}
private void updateBackgroundNotification(int total, int complete) {
notification.setProgress(total, complete, false);
((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE))
.notify(4242, notification.build());
}
private NotificationCompat.Builder initializeBackgroundNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.OTHER);
builder.setSmallIcon(R.drawable.icon_notification);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_notification));
builder.setContentTitle(getString(R.string.ApplicationMigrationService_importing_text_messages));
builder.setContentText(getString(R.string.ApplicationMigrationService_import_in_progress));
builder.setOngoing(true);
builder.setProgress(100, 0, false);
// TODO [greyson] Navigation
builder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0));
stopForeground(true);
startForeground(4242, builder.build());
return builder;
}
private class ImportRunnable implements Runnable {
ImportRunnable() {}
@Override
public void run() {
notification = initializeBackgroundNotification();
PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:migration");
try {
wakeLock.acquire();
setState(new ImportState(ImportState.STATE_MIGRATING_BEGIN, null));
SmsMigrator.migrateDatabase(ApplicationMigrationService.this,
ApplicationMigrationService.this);
setState(new ImportState(ImportState.STATE_MIGRATING_COMPLETE, null));
setDatabaseImported(ApplicationMigrationService.this);
stopForeground(true);
notifyImportComplete();
stopSelf();
} finally {
wakeLock.release();
}
}
}
public class ApplicationMigrationBinder extends Binder {
public ApplicationMigrationService getService() {
return ApplicationMigrationService.this;
}
}
private static class CompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationChannels.OTHER);
builder.setSmallIcon(R.drawable.icon_notification);
builder.setContentTitle(context.getString(R.string.ApplicationMigrationService_import_complete));
builder.setContentText(context.getString(R.string.ApplicationMigrationService_system_database_import_is_complete));
// TODO [greyson] Navigation
builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0));
builder.setWhen(System.currentTimeMillis());
builder.setDefaults(Notification.DEFAULT_VIBRATE);
builder.setAutoCancel(true);
Notification notification = builder.build();
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(31337, notification);
}
}
public static class ImportState {
public static final int STATE_IDLE = 0;
public static final int STATE_MIGRATING_BEGIN = 1;
public static final int STATE_MIGRATING_IN_PROGRESS = 2;
public static final int STATE_MIGRATING_COMPLETE = 3;
public int state;
public ProgressDescription progress;
public ImportState(int state, ProgressDescription progress) {
this.state = state;
this.progress = progress;
}
}
public static boolean isDatabaseImported(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
.getBoolean(DATABASE_MIGRATED, false);
}
public static void setDatabaseImported(Context context) {
context.getSharedPreferences(PREFERENCES_NAME, 0).edit().putBoolean(DATABASE_MIGRATED, true).apply();
}
}

View File

@@ -0,0 +1,17 @@
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(context));
}
}

View File

@@ -0,0 +1,26 @@
package org.thoughtcrime.securesms.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.contacts.ContactsSyncAdapter;
public class ContactsSyncAdapterService extends Service {
private static ContactsSyncAdapter syncAdapter;
@Override
public synchronized void onCreate() {
if (syncAdapter == null) {
syncAdapter = new ContactsSyncAdapter(this, true);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
}

View File

@@ -0,0 +1,95 @@
package org.thoughtcrime.securesms.service;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.view.ContextThemeWrapper;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShareActivity;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BitmapUtil;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
@RequiresApi(api = Build.VERSION_CODES.M)
public class DirectShareService extends ChooserTargetService {
private static final String TAG = DirectShareService.class.getSimpleName();
@Override
public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName,
IntentFilter matchedFilter)
{
List<ChooserTarget> results = new LinkedList<>();
ComponentName componentName = new ComponentName(this, ShareActivity.class);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(this);
Cursor cursor = threadDatabase.getRecentConversationList(10, false);
try {
ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor);
ThreadRecord record;
while ((record = reader.getNext()) != null) {
Recipient recipient = Recipient.resolved(record.getRecipient().getId());
String name = recipient.toShortString(this);
Bitmap avatar;
if (recipient.getContactPhoto() != null) {
try {
avatar = GlideApp.with(this)
.asBitmap()
.load(recipient.getContactPhoto())
.circleCrop()
.submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width))
.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
avatar = getFallbackDrawable(recipient);
}
} else {
avatar = getFallbackDrawable(recipient);
}
Bundle bundle = new Bundle();
bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId());
bundle.putString(ShareActivity.EXTRA_RECIPIENT_ID, recipient.getId().serialize());
bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType());
bundle.setClassLoader(getClassLoader());
results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle));
}
return results;
} finally {
if (cursor != null) cursor.close();
}
}
private Bitmap getFallbackDrawable(@NonNull Recipient recipient) {
Context themedContext = new ContextThemeWrapper(this, R.style.TextSecure_LightTheme);
return BitmapUtil.createFromDrawable(recipient.getFallbackContactPhotoDrawable(themedContext, false),
getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height));
}
}

View File

@@ -0,0 +1,38 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class DirectoryRefreshListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.HOURS.toMillis(12);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getDirectoryRefreshTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
if (scheduledTime != 0 && TextSecurePreferences.isPushRegistered(context)) {
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(true));
}
long newTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setDirectoryRefreshTime(context, newTime);
return newTime;
}
public static void schedule(Context context) {
new DirectoryRefreshListener().onReceive(context, new Intent());
}
}

View File

@@ -0,0 +1,26 @@
package org.thoughtcrime.securesms.service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
public class ExpirationListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ApplicationContext.getInstance(context).getExpiringMessageManager().checkSchedule();
}
public static void setAlarm(Context context, long waitTimeMillis) {
Intent intent = new Intent(context, ExpirationListener.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + waitTimeMillis, pendingIntent);
}
}

View File

@@ -0,0 +1,152 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Comparator;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class ExpiringMessageManager {
private static final String TAG = ExpiringMessageManager.class.getSimpleName();
private final TreeSet<ExpiringMessageReference> expiringMessageReferences = new TreeSet<>(new ExpiringMessageComparator());
private final Executor executor = Executors.newSingleThreadExecutor();
private final SmsDatabase smsDatabase;
private final MmsDatabase mmsDatabase;
private final Context context;
public ExpiringMessageManager(Context context) {
this.context = context.getApplicationContext();
this.smsDatabase = DatabaseFactory.getSmsDatabase(context);
this.mmsDatabase = DatabaseFactory.getMmsDatabase(context);
executor.execute(new LoadTask());
executor.execute(new ProcessTask());
}
public void scheduleDeletion(long id, boolean mms, long expiresInMillis) {
scheduleDeletion(id, mms, System.currentTimeMillis(), expiresInMillis);
}
public void scheduleDeletion(long id, boolean mms, long startedAtTimestamp, long expiresInMillis) {
long expiresAtMillis = startedAtTimestamp + expiresInMillis;
synchronized (expiringMessageReferences) {
expiringMessageReferences.add(new ExpiringMessageReference(id, mms, expiresAtMillis));
expiringMessageReferences.notifyAll();
}
}
public void checkSchedule() {
synchronized (expiringMessageReferences) {
expiringMessageReferences.notifyAll();
}
}
private class LoadTask implements Runnable {
public void run() {
SmsDatabase.Reader smsReader = smsDatabase.readerFor(smsDatabase.getExpirationStartedMessages());
MmsDatabase.Reader mmsReader = mmsDatabase.getExpireStartedMessages();
MessageRecord messageRecord;
while ((messageRecord = smsReader.getNext()) != null) {
expiringMessageReferences.add(new ExpiringMessageReference(messageRecord.getId(),
messageRecord.isMms(),
messageRecord.getExpireStarted() + messageRecord.getExpiresIn()));
}
while ((messageRecord = mmsReader.getNext()) != null) {
expiringMessageReferences.add(new ExpiringMessageReference(messageRecord.getId(),
messageRecord.isMms(),
messageRecord.getExpireStarted() + messageRecord.getExpiresIn()));
}
smsReader.close();
mmsReader.close();
}
}
@SuppressWarnings("InfiniteLoopStatement")
private class ProcessTask implements Runnable {
public void run() {
while (true) {
ExpiringMessageReference expiredMessage = null;
synchronized (expiringMessageReferences) {
try {
while (expiringMessageReferences.isEmpty()) expiringMessageReferences.wait();
ExpiringMessageReference nextReference = expiringMessageReferences.first();
long waitTime = nextReference.expiresAtMillis - System.currentTimeMillis();
if (waitTime > 0) {
ExpirationListener.setAlarm(context, waitTime);
expiringMessageReferences.wait(waitTime);
} else {
expiredMessage = nextReference;
expiringMessageReferences.remove(nextReference);
}
} catch (InterruptedException e) {
Log.w(TAG, e);
}
}
if (expiredMessage != null) {
if (expiredMessage.mms) mmsDatabase.delete(expiredMessage.id);
else smsDatabase.deleteMessage(expiredMessage.id);
}
}
}
}
private static class ExpiringMessageReference {
private final long id;
private final boolean mms;
private final long expiresAtMillis;
private ExpiringMessageReference(long id, boolean mms, long expiresAtMillis) {
this.id = id;
this.mms = mms;
this.expiresAtMillis = expiresAtMillis;
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof ExpiringMessageReference)) return false;
ExpiringMessageReference that = (ExpiringMessageReference)other;
return this.id == that.id && this.mms == that.mms && this.expiresAtMillis == that.expiresAtMillis;
}
@Override
public int hashCode() {
return (int)this.id ^ (mms ? 1 : 0) ^ (int)expiresAtMillis;
}
}
private static class ExpiringMessageComparator implements Comparator<ExpiringMessageReference> {
@Override
public int compare(ExpiringMessageReference lhs, ExpiringMessageReference rhs) {
if (lhs.expiresAtMillis < rhs.expiresAtMillis) return -1;
else if (lhs.expiresAtMillis > rhs.expiresAtMillis) return 1;
else if (lhs.id < rhs.id) return -1;
else if (lhs.id > rhs.id) return 1;
else if (!lhs.mms && rhs.mms) return -1;
else if (lhs.mms && !rhs.mms) return 1;
else return 0;
}
}
}

View File

@@ -0,0 +1,254 @@
package org.thoughtcrime.securesms.service;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.MainActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
public final class GenericForegroundService extends Service {
private static final String TAG = Log.tag(GenericForegroundService.class);
private final IBinder binder = new LocalBinder();
private static final int NOTIFICATION_ID = 827353982;
private static final String EXTRA_TITLE = "extra_title";
private static final String EXTRA_CHANNEL_ID = "extra_channel_id";
private static final String EXTRA_ICON_RES = "extra_icon_res";
private static final String EXTRA_ID = "extra_id";
private static final String EXTRA_PROGRESS = "extra_progress";
private static final String EXTRA_PROGRESS_MAX = "extra_progress_max";
private static final String EXTRA_PROGRESS_INDETERMINATE = "extra_progress_indeterminate";
private static final String ACTION_START = "start";
private static final String ACTION_STOP = "stop";
private static final AtomicInteger NEXT_ID = new AtomicInteger();
private final LinkedHashMap<Integer, Entry> allActiveMessages = new LinkedHashMap<>();
private static final Entry DEFAULTS = new Entry("", NotificationChannels.OTHER, R.drawable.ic_signal_grey_24dp, -1, 0, 0, false);
private @Nullable Entry lastPosted;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
throw new IllegalStateException("Intent needs to be non-null.");
}
synchronized (GenericForegroundService.class) {
String action = intent.getAction();
if (ACTION_START.equals(action)) handleStart(intent);
else if (ACTION_STOP .equals(action)) handleStop(intent);
else throw new IllegalStateException(String.format("Action needs to be %s or %s.", ACTION_START, ACTION_STOP));
updateNotification();
return START_NOT_STICKY;
}
}
private synchronized void updateNotification() {
Iterator<Entry> iterator = allActiveMessages.values().iterator();
if (iterator.hasNext()) {
postObligatoryForegroundNotification(iterator.next());
} else {
Log.i(TAG, "Last request. Ending foreground service.");
postObligatoryForegroundNotification(lastPosted != null ? lastPosted : DEFAULTS);
stopForeground(true);
stopSelf();
}
}
private synchronized void handleStart(@NonNull Intent intent) {
Entry entry = Entry.fromIntent(intent);
Log.i(TAG, String.format(Locale.US, "handleStart() %s", entry));
allActiveMessages.put(entry.id, entry);
}
private synchronized void handleStop(@NonNull Intent intent) {
Log.i(TAG, "handleStop()");
int id = intent.getIntExtra(EXTRA_ID, -1);
Entry removed = allActiveMessages.remove(id);
if (removed == null) {
Log.w(TAG, "Could not find entry to remove");
}
}
private void postObligatoryForegroundNotification(@NonNull Entry active) {
lastPosted = active;
// TODO [greyson] Navigation
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, active.channelId)
.setSmallIcon(active.iconRes)
.setContentTitle(active.title)
.setProgress(active.progressMax, active.progress, active.indeterminate)
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0))
.build());
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) {
return startForegroundTask(context, task, DEFAULTS.channelId);
}
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId) {
return startForegroundTask(context, task, channelId, DEFAULTS.iconRes);
}
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId, @DrawableRes int iconRes) {
final int id = NEXT_ID.getAndIncrement();
Intent intent = new Intent(context, GenericForegroundService.class);
intent.setAction(ACTION_START);
intent.putExtra(EXTRA_TITLE, task);
intent.putExtra(EXTRA_CHANNEL_ID, channelId);
intent.putExtra(EXTRA_ICON_RES, iconRes);
intent.putExtra(EXTRA_ID, id);
ContextCompat.startForegroundService(context, intent);
return new NotificationController(context, id);
}
public static void stopForegroundTask(@NonNull Context context, int id) {
Intent intent = new Intent(context, GenericForegroundService.class);
intent.setAction(ACTION_STOP);
intent.putExtra(EXTRA_ID, id);
ContextCompat.startForegroundService(context, intent);
}
synchronized void replaceProgress(int id, int progressMax, int progress, boolean indeterminate) {
Entry oldEntry = allActiveMessages.get(id);
if (oldEntry == null) {
Log.w(TAG, "Failed to replace notification, it was not found");
return;
}
Entry newEntry = new Entry(oldEntry.title, oldEntry.channelId, oldEntry.iconRes, oldEntry.id, progressMax, progress, indeterminate);
if (oldEntry.equals(newEntry)) {
Log.d(TAG, String.format("handleReplace() skip, no change %s", newEntry));
return;
}
Log.i(TAG, String.format("handleReplace() %s", newEntry));
allActiveMessages.put(newEntry.id, newEntry);
updateNotification();
}
private static class Entry {
final @NonNull String title;
final @NonNull String channelId;
final int id;
final @DrawableRes int iconRes;
final int progress;
final int progressMax;
final boolean indeterminate;
private Entry(@NonNull String title, @NonNull String channelId, @DrawableRes int iconRes, int id, int progressMax, int progress, boolean indeterminate) {
this.title = title;
this.channelId = channelId;
this.iconRes = iconRes;
this.id = id;
this.progress = progress;
this.progressMax = progressMax;
this.indeterminate = indeterminate;
}
private static Entry fromIntent(@NonNull Intent intent) {
int id = intent.getIntExtra(EXTRA_ID, DEFAULTS.id);
String title = intent.getStringExtra(EXTRA_TITLE);
if (title == null) title = DEFAULTS.title;
String channelId = intent.getStringExtra(EXTRA_CHANNEL_ID);
if (channelId == null) channelId = DEFAULTS.channelId;
int iconRes = intent.getIntExtra(EXTRA_ICON_RES, DEFAULTS.iconRes);
int progress = intent.getIntExtra(EXTRA_PROGRESS, DEFAULTS.progress);
int progressMax = intent.getIntExtra(EXTRA_PROGRESS_MAX, DEFAULTS.progressMax);
boolean indeterminate = intent.getBooleanExtra(EXTRA_PROGRESS_INDETERMINATE, DEFAULTS.indeterminate);
return new Entry(title, channelId, iconRes, id, progressMax, progress, indeterminate);
}
@Override
public @NonNull String toString() {
return String.format(Locale.US, "ChannelId: %s Id: %d Progress: %d/%d %s", channelId, id, progress, progressMax, indeterminate ? "indeterminate" : "determinate");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return id == entry.id &&
iconRes == entry.iconRes &&
progress == entry.progress &&
progressMax == entry.progressMax &&
indeterminate == entry.indeterminate &&
Objects.equals(title, entry.title) &&
Objects.equals(channelId, entry.channelId);
}
@Override
public int hashCode() {
int hashCode = title.hashCode();
hashCode *= 31;
hashCode += channelId.hashCode();
hashCode *= 31;
hashCode += id;
hashCode *= 31;
hashCode += iconRes;
hashCode *= 31;
hashCode += progress;
hashCode *= 31;
hashCode += progressMax;
hashCode *= 31;
hashCode += indeterminate ? 1 : 0;
return hashCode;
}
}
class LocalBinder extends Binder {
GenericForegroundService getService() {
// Return this instance of LocalService so clients can call public methods
return GenericForegroundService.this;
}
}
}

View File

@@ -0,0 +1,210 @@
package org.thoughtcrime.securesms.service;
import android.app.Service;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.IncomingMessageProcessor.Processor;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class IncomingMessageObserver implements ConstraintObserver.Notifier {
private static final String TAG = IncomingMessageObserver.class.getSimpleName();
public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private static SignalServiceMessagePipe pipe = null;
private static SignalServiceMessagePipe unidentifiedPipe = null;
private final Context context;
private final NetworkConstraint networkConstraint;
private final SignalServiceNetworkAccess networkAccess;
private boolean appVisible;
public IncomingMessageObserver(@NonNull Context context) {
this.context = context;
this.networkConstraint = new NetworkConstraint.Factory(ApplicationContext.getInstance(context)).create();
this.networkAccess = ApplicationDependencies.getSignalServiceNetworkAccess();
new NetworkConstraintObserver(ApplicationContext.getInstance(context)).register(this);
new MessageRetrievalThread().start();
if (TextSecurePreferences.isFcmDisabled(context)) {
ContextCompat.startForegroundService(context, new Intent(context, ForegroundService.class));
}
ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onStart(@NonNull LifecycleOwner owner) {
onAppForegrounded();
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
onAppBackgrounded();
}
});
}
@Override
public void onConstraintMet(@NonNull String reason) {
synchronized (this) {
notifyAll();
}
}
private synchronized void onAppForegrounded() {
appVisible = true;
notifyAll();
}
private synchronized void onAppBackgrounded() {
appVisible = false;
notifyAll();
}
private synchronized boolean isConnectionNecessary() {
boolean isGcmDisabled = TextSecurePreferences.isFcmDisabled(context);
Log.d(TAG, String.format("Network requirement: %s, app visible: %s, gcm disabled: %b",
networkConstraint.isMet(), appVisible, isGcmDisabled));
return TextSecurePreferences.isPushRegistered(context) &&
TextSecurePreferences.isWebsocketRegistered(context) &&
(appVisible || isGcmDisabled) &&
networkConstraint.isMet() &&
!networkAccess.isCensored(context);
}
private synchronized void waitForConnectionNecessary() {
try {
while (!isConnectionNecessary()) wait();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
try {
pipe.shutdown();
unidentifiedPipe.shutdown();
} catch (Throwable t) {
Log.w(TAG, t);
}
}
public static @Nullable SignalServiceMessagePipe getPipe() {
return pipe;
}
public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() {
return unidentifiedPipe;
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
MessageRetrievalThread() {
super("MessageRetrievalService");
setUncaughtExceptionHandler(this);
}
@Override
public void run() {
while (true) {
Log.i(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
Log.i(TAG, "Making websocket connection....");
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
pipe = receiver.createMessagePipe();
unidentifiedPipe = receiver.createUnidentifiedMessagePipe();
SignalServiceMessagePipe localPipe = pipe;
SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe;
try {
while (isConnectionNecessary()) {
try {
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
Log.i(TAG, "Retrieved envelope! " + envelope.getSourceIdentifier());
try (Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) {
processor.processEnvelope(envelope);
}
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe, unidentifiedLocalPipe);
}
Log.i(TAG, "Looping...");
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.w(TAG, "*** Uncaught exception!");
Log.w(TAG, e);
}
}
public static class ForegroundService extends Service {
@Override
public @Nullable IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), NotificationChannels.OTHER);
builder.setContentTitle(getApplicationContext().getString(R.string.MessageRetrievalService_signal));
builder.setContentText(getApplicationContext().getString(R.string.MessageRetrievalService_background_connection_enabled));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setWhen(0);
builder.setSmallIcon(R.drawable.ic_signal_background_connection);
startForeground(FOREGROUND_ID, builder.build());
return Service.START_STICKY;
}
}
}

View File

@@ -0,0 +1,309 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.MainActivity;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.DummyActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
/**
* Small service that stays running to keep a key cached in memory.
*
* @author Moxie Marlinspike
*/
public class KeyCachingService extends Service {
private static final String TAG = KeyCachingService.class.getSimpleName();
public static final int SERVICE_RUNNING_ID = 4141;
public static final String KEY_PERMISSION = "org.thoughtcrime.securesms.ACCESS_SECRETS";
public static final String NEW_KEY_EVENT = "org.thoughtcrime.securesms.service.action.NEW_KEY_EVENT";
public static final String CLEAR_KEY_EVENT = "org.thoughtcrime.securesms.service.action.CLEAR_KEY_EVENT";
public static final String LOCK_TOGGLED_EVENT = "org.thoughtcrime.securesms.service.action.LOCK_ENABLED_EVENT";
private static final String PASSPHRASE_EXPIRED_EVENT = "org.thoughtcrime.securesms.service.action.PASSPHRASE_EXPIRED_EVENT";
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
public static final String DISABLE_ACTION = "org.thoughtcrime.securesms.service.action.DISABLE";
public static final String LOCALE_CHANGE_EVENT = "org.thoughtcrime.securesms.service.action.LOCALE_CHANGE_EVENT";
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private final IBinder binder = new KeySetBinder();
private static MasterSecret masterSecret;
public KeyCachingService() {}
public static synchronized boolean isLocked(Context context) {
return masterSecret == null && (!TextSecurePreferences.isPasswordDisabled(context) || TextSecurePreferences.isScreenLockEnabled(context));
}
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
try {
return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
} catch (InvalidPassphraseException e) {
Log.w("KeyCachingService", e);
}
}
return masterSecret;
}
public static void onAppForegrounded(@NonNull Context context) {
ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context));
}
public static void onAppBackgrounded(@NonNull Context context) {
startTimeoutIfAppropriate(context);
}
@SuppressLint("StaticFieldLeak")
public void setMasterSecret(final MasterSecret masterSecret) {
synchronized (KeyCachingService.class) {
KeyCachingService.masterSecret = masterSecret;
foregroundService();
broadcastNewSecret();
startTimeoutIfAppropriate(this);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (!ApplicationMigrations.isUpdate(KeyCachingService.this)) {
MessageNotifier.updateNotification(KeyCachingService.this);
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) return START_NOT_STICKY;
Log.d(TAG, "onStartCommand, " + intent.getAction());
if (intent.getAction() != null) {
switch (intent.getAction()) {
case CLEAR_KEY_ACTION: handleClearKey(); break;
case PASSPHRASE_EXPIRED_EVENT: handleClearKey(); break;
case DISABLE_ACTION: handleDisableService(); break;
case LOCALE_CHANGE_EVENT: handleLocaleChanged(); break;
case LOCK_TOGGLED_EVENT: handleLockToggled(); break;
}
}
return START_NOT_STICKY;
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate()");
super.onCreate();
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException e) {
Log.w("KeyCachingService", e);
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "KCS Is Being Destroyed!");
handleClearKey();
}
/**
* Workaround for Android bug:
* https://code.google.com/p/android/issues/detail?id=53313
*/
@Override
public void onTaskRemoved(Intent rootIntent) {
Intent intent = new Intent(this, DummyActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
@SuppressLint("StaticFieldLeak")
private void handleClearKey() {
Log.i(TAG, "handleClearKey()");
KeyCachingService.masterSecret = null;
stopForeground(true);
Intent intent = new Intent(CLEAR_KEY_EVENT);
intent.setPackage(getApplicationContext().getPackageName());
sendBroadcast(intent, KEY_PERMISSION);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
MessageNotifier.updateNotification(KeyCachingService.this);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void handleLockToggled() {
stopForeground(true);
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException e) {
Log.w(TAG, e);
}
}
private void handleDisableService() {
if (TextSecurePreferences.isPasswordDisabled(this) &&
!TextSecurePreferences.isScreenLockEnabled(this))
{
stopForeground(true);
}
}
private void handleLocaleChanged() {
dynamicLanguage.updateServiceLocale(this);
foregroundService();
}
private static void startTimeoutIfAppropriate(@NonNull Context context) {
boolean appVisible = ApplicationContext.getInstance(context).isAppVisible();
boolean secretSet = KeyCachingService.masterSecret != null;
boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(context);
boolean passLockActive = timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(context);
long screenTimeout = TextSecurePreferences.getScreenLockTimeout(context);
boolean screenLockActive = screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(context);
if (!appVisible && secretSet && (passLockActive || screenLockActive)) {
long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(context);
long screenLockTimeoutSeconds = TextSecurePreferences.getScreenLockTimeout(context);
long timeoutMillis;
if (!TextSecurePreferences.isPasswordDisabled(context)) timeoutMillis = TimeUnit.MINUTES.toMillis(passphraseTimeoutMinutes);
else timeoutMillis = TimeUnit.SECONDS.toMillis(screenLockTimeoutSeconds);
Log.i(TAG, "Starting timeout: " + timeoutMillis);
AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
PendingIntent expirationIntent = buildExpirationPendingIntent(context);
alarmManager.cancel(expirationIntent);
alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeoutMillis, expirationIntent);
}
}
private void foregroundService() {
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
stopForeground(true);
return;
}
Log.i(TAG, "foregrounding KCS");
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.LOCKED_STATUS);
builder.setContentTitle(getString(R.string.KeyCachingService_passphrase_cached));
builder.setContentText(getString(R.string.KeyCachingService_signal_passphrase_cached));
builder.setSmallIcon(R.drawable.icon_cached);
builder.setWhen(0);
builder.setPriority(Notification.PRIORITY_MIN);
builder.addAction(R.drawable.ic_menu_lock_dark, getString(R.string.KeyCachingService_lock), buildLockIntent());
builder.setContentIntent(buildLaunchIntent());
stopForeground(true);
startForeground(SERVICE_RUNNING_ID, builder.build());
}
private void broadcastNewSecret() {
Log.i(TAG, "Broadcasting new secret...");
Intent intent = new Intent(NEW_KEY_EVENT);
intent.setPackage(getApplicationContext().getPackageName());
sendBroadcast(intent, KEY_PERMISSION);
}
private PendingIntent buildLockIntent() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(PASSPHRASE_EXPIRED_EVENT);
return PendingIntent.getService(getApplicationContext(), 0, intent, 0);
}
private PendingIntent buildLaunchIntent() {
// TODO [greyson] Navigation
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
return PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
}
private static PendingIntent buildExpirationPendingIntent(@NonNull Context context) {
Intent expirationIntent = new Intent(PASSPHRASE_EXPIRED_EVENT, null, context, KeyCachingService.class);
return PendingIntent.getService(context, 0, expirationIntent, 0);
}
@Override
public IBinder onBind(Intent arg0) {
return binder;
}
public class KeySetBinder extends Binder {
public KeyCachingService getService() {
return KeyCachingService.this;
}
}
}

View File

@@ -0,0 +1,45 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class LocalBackupListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.DAYS.toMillis(1);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getNextBackupTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
if (TextSecurePreferences.isBackupEnabled(context)) {
ApplicationDependencies.getJobManager().add(new LocalBackupJob());
}
return setNextBackupTimeToIntervalFromNow(context);
}
public static void schedule(Context context) {
if (TextSecurePreferences.isBackupEnabled(context)) {
new LocalBackupListener().onReceive(context, new Intent());
}
}
public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) {
long nextTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setNextBackupTime(context, nextTime);
return nextTime;
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.Telephony;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.MmsReceiveJob;
import org.thoughtcrime.securesms.util.Util;
public class MmsListener extends BroadcastReceiver {
private static final String TAG = MmsListener.class.getSimpleName();
private boolean isRelevant(Context context, Intent intent) {
if (!ApplicationMigrationService.isDatabaseImported(context)) {
return false;
}
if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction()) && Util.isDefaultSmsProvider(context)) {
return false;
}
return false;
}
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Got MMS broadcast..." + intent.getAction());
if ((Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION.equals(intent.getAction()) &&
Util.isDefaultSmsProvider(context)) ||
(Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction()) &&
isRelevant(context, intent)))
{
Log.i(TAG, "Relevant!");
int subscriptionId = intent.getExtras().getInt("subscription", -1);
ApplicationDependencies.getJobManager().add(new MmsReceiveJob(intent.getByteArrayExtra("data"), subscriptionId));
abortBroadcast();
}
}
}

View File

@@ -0,0 +1,90 @@
package org.thoughtcrime.securesms.service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import androidx.annotation.NonNull;
import java.util.concurrent.atomic.AtomicReference;
public final class NotificationController implements AutoCloseable {
private final @NonNull Context context;
private final int id;
private int progress;
private int progressMax;
private boolean indeterminate;
private long percent = -1;
private final AtomicReference<GenericForegroundService> service = new AtomicReference<>();
NotificationController(@NonNull Context context, int id) {
this.context = context;
this.id = id;
bindToService();
}
private void bindToService() {
context.bindService(new Intent(context, GenericForegroundService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
GenericForegroundService.LocalBinder binder = (GenericForegroundService.LocalBinder) service;
GenericForegroundService genericForegroundService = binder.getService();
NotificationController.this.service.set(genericForegroundService);
updateProgressOnService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
service.set(null);
}
}, Context.BIND_AUTO_CREATE);
}
public int getId() {
return id;
}
@Override
public void close() {
GenericForegroundService.stopForegroundTask(context, id);
}
public void setIndeterminateProgress() {
setProgress(0, 0, true);
}
public void setProgress(long newProgressMax, long newProgress) {
setProgress((int) newProgressMax, (int) newProgress, false);
}
private synchronized void setProgress(int newProgressMax, int newProgress, boolean indeterminant) {
int newPercent = newProgressMax != 0 ? 100 * newProgress / newProgressMax : -1;
boolean same = newPercent == percent && indeterminate == indeterminant;
percent = newPercent;
progress = newProgress;
progressMax = newProgressMax;
indeterminate = indeterminant;
if (same) return;
updateProgressOnService();
}
private synchronized void updateProgressOnService() {
GenericForegroundService genericForegroundService = service.get();
if (genericForegroundService == null) return;
genericForegroundService.replaceProgress(id, progressMax, progress, indeterminate);
}
}

View File

@@ -0,0 +1,29 @@
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
/**
* Respond to a PanicKit trigger Intent by locking the app. PanicKit provides a
* common framework for creating "panic button" apps that can trigger actions
* in "panic responder" apps. In this case, the response is to lock the app,
* if it has been configured to do so via the Signal lock preference. If the
* user has not set a passphrase, then the panic trigger intent does nothing.
*/
public class PanicResponderListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && !TextSecurePreferences.isPasswordDisabled(context) &&
"info.guardianproject.panic.action.TRIGGER".equals(intent.getAction()))
{
Intent lockIntent = new Intent(context, KeyCachingService.class);
lockIntent.setAction(KeyCachingService.CLEAR_KEY_ACTION);
context.startService(lockIntent);
}
}
}

View File

@@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.logging.Log;
public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
private static final String TAG = PersistentAlarmManagerListener.class.getSimpleName();
protected abstract long getNextScheduledExecutionTime(Context context);
protected abstract long onAlarm(Context context, long scheduledTime);
@Override
public void onReceive(Context context, Intent intent) {
long scheduledTime = getNextScheduledExecutionTime(context);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent(context, getClass());
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0);
if (System.currentTimeMillis() >= scheduledTime) {
scheduledTime = onAlarm(context, scheduledTime);
}
Log.i(TAG, getClass() + " scheduling for: " + scheduledTime);
alarmManager.cancel(pendingIntent);
alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
}
}

View File

@@ -0,0 +1,20 @@
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.logging.Log;
public class PersistentConnectionBootListener extends BroadcastReceiver {
private static final String TAG = PersistentConnectionBootListener.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Log.i(TAG, "Received boot event. Application should be started, allowing non-GCM devices to start a foreground service.");
}
}
}

View File

@@ -0,0 +1,61 @@
package org.thoughtcrime.securesms.service;
import android.app.IntentService;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.widget.Toast;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.Rfc5724Uri;
import java.net.URISyntaxException;
import java.net.URLDecoder;
public class QuickResponseService extends IntentService {
private static final String TAG = QuickResponseService.class.getSimpleName();
public QuickResponseService() {
super("QuickResponseService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(intent.getAction())) {
Log.w(TAG, "Received unknown intent: " + intent.getAction());
return;
}
if (KeyCachingService.isLocked(this)) {
Log.w(TAG, "Got quick response request when locked...");
Toast.makeText(this, R.string.QuickResponseService_quick_response_unavailable_when_Signal_is_locked, Toast.LENGTH_LONG).show();
return;
}
try {
Rfc5724Uri uri = new Rfc5724Uri(intent.getDataString());
String content = intent.getStringExtra(Intent.EXTRA_TEXT);
String number = uri.getPath();
if (number.contains("%")){
number = URLDecoder.decode(number);
}
Recipient recipient = Recipient.external(this, number);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
long expiresIn = recipient.getExpireMessages() * 1000L;
if (!TextUtils.isEmpty(content)) {
MessageSender.send(this, new OutgoingTextMessage(recipient, content, expiresIn, subscriptionId), -1, false, null);
}
} catch (URISyntaxException e) {
Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show();
Log.w(TAG, e);
}
}
}

View File

@@ -0,0 +1,37 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class RotateSenderCertificateListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.DAYS.toMillis(1);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getUnidentifiedAccessCertificateRotationTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
ApplicationDependencies.getJobManager().add(new RotateCertificateJob(context));
long nextTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setUnidentifiedAccessCertificateRotationTime(context, nextTime);
return nextTime;
}
public static void schedule(Context context) {
new RotateSenderCertificateListener().onReceive(context, new Intent());
}
}

View File

@@ -0,0 +1,38 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class RotateSignedPreKeyListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.DAYS.toMillis(2);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getSignedPreKeyRotationTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
if (scheduledTime != 0 && TextSecurePreferences.isPushRegistered(context)) {
ApplicationDependencies.getJobManager().add(new RotateSignedPreKeyJob());
}
long nextTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setSignedPreKeyRotationTime(context, nextTime);
return nextTime;
}
public static void schedule(Context context) {
new RotateSignedPreKeyListener().onReceive(context, new Intent());
}
}

View File

@@ -0,0 +1,70 @@
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.jobs.SmsSentJob;
public class SmsDeliveryListener extends BroadcastReceiver {
private static final String TAG = SmsDeliveryListener.class.getSimpleName();
public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION";
public static final String DELIVERED_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION";
@Override
public void onReceive(Context context, Intent intent) {
JobManager jobManager = ApplicationDependencies.getJobManager();
long messageId = intent.getLongExtra("message_id", -1);
int runAttempt = intent.getIntExtra("run_attempt", 0);
switch (intent.getAction()) {
case SENT_SMS_ACTION:
int result = getResultCode();
jobManager.add(new SmsSentJob(messageId, SENT_SMS_ACTION, result, runAttempt));
break;
case DELIVERED_SMS_ACTION:
byte[] pdu = intent.getByteArrayExtra("pdu");
if (pdu == null) {
Log.w(TAG, "No PDU in delivery receipt!");
break;
}
SmsMessage message = SmsMessage.createFromPdu(pdu);
if (message == null) {
Log.w(TAG, "Delivery receipt failed to parse!");
break;
}
int status = message.getStatus();
Log.i(TAG, "Original status: " + status);
// Note: https://developer.android.com/reference/android/telephony/SmsMessage.html#getStatus()
// " CDMA: For not interfering with status codes from GSM, the value is shifted to the bits 31-16"
// Note: https://stackoverflow.com/a/33240109
if ("3gpp2".equals(intent.getStringExtra("format"))) {
Log.w(TAG, "Correcting for CDMA delivery receipt...");
if (status >> 24 <= 0) status = SmsDatabase.Status.STATUS_COMPLETE;
else if (status >> 24 == 2) status = SmsDatabase.Status.STATUS_PENDING;
else if (status >> 24 == 3) status = SmsDatabase.Status.STATUS_FAILED;
}
jobManager.add(new SmsSentJob(messageId, DELIVERED_SMS_ACTION, status, runAttempt));
break;
default:
Log.w(TAG, "Unknown action: " + intent.getAction());
}
}
}

View File

@@ -0,0 +1,115 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Telephony;
import android.telephony.SmsMessage;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.SmsReceiveJob;
import org.thoughtcrime.securesms.util.Util;
public class SmsListener extends BroadcastReceiver {
private static final String SMS_RECEIVED_ACTION = Telephony.Sms.Intents.SMS_RECEIVED_ACTION;
private static final String SMS_DELIVERED_ACTION = Telephony.Sms.Intents.SMS_DELIVER_ACTION;
private boolean isExemption(SmsMessage message, String messageBody) {
// ignore CLASS0 ("flash") messages
if (message.getMessageClass() == SmsMessage.MessageClass.CLASS_0)
return true;
// ignore OTP messages from Sparebank1 (Norwegian bank)
if (messageBody.startsWith("Sparebank1://otp?")) {
return true;
}
return
message.getOriginatingAddress().length() < 7 &&
(messageBody.toUpperCase().startsWith("//ANDROID:") || // Sprint Visual Voicemail
messageBody.startsWith("//BREW:")); //BREW stands for “Binary Runtime Environment for Wireless"
}
private SmsMessage getSmsMessageFromIntent(Intent intent) {
Bundle bundle = intent.getExtras();
Object[] pdus = (Object[])bundle.get("pdus");
if (pdus == null || pdus.length == 0)
return null;
return SmsMessage.createFromPdu((byte[])pdus[0]);
}
private String getSmsMessageBodyFromIntent(Intent intent) {
Bundle bundle = intent.getExtras();
Object[] pdus = (Object[])bundle.get("pdus");
StringBuilder bodyBuilder = new StringBuilder();
if (pdus == null)
return null;
for (Object pdu : pdus)
bodyBuilder.append(SmsMessage.createFromPdu((byte[])pdu).getDisplayMessageBody());
return bodyBuilder.toString();
}
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 (SMS_RECEIVED_ACTION.equals(intent.getAction()) && Util.isDefaultSmsProvider(context)) {
return false;
}
return false;
}
@Override
public void onReceive(Context context, Intent intent) {
Log.i("SMSListener", "Got SMS broadcast...");
if ((intent.getAction().equals(SMS_DELIVERED_ACTION)) ||
(intent.getAction().equals(SMS_RECEIVED_ACTION)) && isRelevant(context, intent))
{
Log.i("SmsListener", "Constructing SmsReceiveJob...");
Object[] pdus = (Object[]) intent.getExtras().get("pdus");
int subscriptionId = intent.getExtras().getInt("subscription", -1);
ApplicationDependencies.getJobManager().add(new SmsReceiveJob(pdus, subscriptionId));
abortBroadcast();
}
}
}

View File

@@ -0,0 +1,93 @@
package org.thoughtcrime.securesms.service;
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.thoughtcrime.securesms.util.ServiceUtil;
/**
* Class to help manage scheduling events to happen in the future, whether the app is open or not.
*/
public abstract class TimedEventManager<E> {
private final Application application;
private final Handler handler;
public TimedEventManager(@NonNull Application application, @NonNull String threadName) {
HandlerThread handlerThread = new HandlerThread(threadName);
handlerThread.start();
this.application = application;
this.handler = new Handler(handlerThread.getLooper());
}
/**
* Should be called whenever the underlying data of events has changed. Will appropriately
* schedule new event executions.
*/
public void scheduleIfNecessary() {
handler.removeCallbacksAndMessages(null);
handler.post(() -> {
E event = getNextClosestEvent();
if (event != null) {
long delay = getDelayForEvent(event);
handler.postDelayed(() -> {
executeEvent(event);
scheduleIfNecessary();
}, delay);
scheduleAlarm(application, delay);
}
});
}
/**
* @return The next event that should be executed, or {@code null} if there are no events to execute.
*/
@WorkerThread
protected @Nullable abstract E getNextClosestEvent();
/**
* Execute the provided event.
*/
@WorkerThread
protected abstract void executeEvent(@NonNull E event);
/**
* @return How long before the provided event should be executed.
*/
@WorkerThread
protected abstract long getDelayForEvent(@NonNull E event);
/**
* Schedules an alarm to call {@link #scheduleIfNecessary()} after the specified delay. You can
* use {@link #setAlarm(Context, long, Class)} as a helper method.
*/
@AnyThread
protected abstract void scheduleAlarm(@NonNull Application application, long delay);
/**
* Helper method to set an alarm.
*/
protected static void setAlarm(@NonNull Context context, long delay, @NonNull Class alarmClass) {
Intent intent = new Intent(context, alarmClass);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
alarmManager.cancel(pendingIntent);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, pendingIntent);
}
}

View File

@@ -0,0 +1,120 @@
package org.thoughtcrime.securesms.service;
import android.app.DownloadManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.FileProviderUtil;
import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
public class UpdateApkReadyListener extends BroadcastReceiver {
private static final String TAG = UpdateApkReadyListener.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive()");
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -2);
if (downloadId == TextSecurePreferences.getUpdateApkDownloadId(context)) {
Uri uri = getLocalUriForDownloadId(context, downloadId);
String encodedDigest = TextSecurePreferences.getUpdateApkDigest(context);
if (uri == null) {
Log.w(TAG, "Downloaded local URI is null?");
return;
}
if (isMatchingDigest(context, downloadId, encodedDigest)) {
displayInstallNotification(context, uri);
} else {
Log.w(TAG, "Downloaded APK doesn't match digest...");
}
}
}
}
private void displayInstallNotification(Context context, Uri uri) {
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setData(uri);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(context, NotificationChannels.APP_UPDATES)
.setOngoing(true)
.setContentTitle(context.getString(R.string.UpdateApkReadyListener_Signal_update))
.setContentText(context.getString(R.string.UpdateApkReadyListener_a_new_version_of_signal_is_available_tap_to_update))
.setSmallIcon(R.drawable.icon_notification)
.setColor(context.getResources().getColor(R.color.textsecure_primary))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.setContentIntent(pendingIntent)
.build();
ServiceUtil.getNotificationManager(context).notify(666, notification);
}
private @Nullable Uri getLocalUriForDownloadId(Context context, long downloadId) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor cursor = downloadManager.query(query);
try {
if (cursor != null && cursor.moveToFirst()) {
String localUri = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
if (localUri != null) {
File localFile = new File(Uri.parse(localUri).getPath());
return FileProviderUtil.getUriFor(context, localFile);
}
}
} finally {
if (cursor != null) cursor.close();
}
return null;
}
private boolean isMatchingDigest(Context context, long downloadId, String theirEncodedDigest) {
try {
if (theirEncodedDigest == null) return false;
byte[] theirDigest = Hex.fromStringCondensed(theirEncodedDigest);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
FileInputStream fin = new FileInputStream(downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
byte[] ourDigest = FileUtils.getFileDigest(fin);
fin.close();
return MessageDigest.isEqual(ourDigest, theirDigest);
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
}
}

View File

@@ -0,0 +1,47 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class UpdateApkRefreshListener extends PersistentAlarmManagerListener {
private static final String TAG = UpdateApkRefreshListener.class.getSimpleName();
private static final long INTERVAL = TimeUnit.HOURS.toMillis(6);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getUpdateApkRefreshTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
Log.i(TAG, "onAlarm...");
if (scheduledTime != 0 && BuildConfig.PLAY_STORE_DISABLED) {
Log.i(TAG, "Queueing APK update job...");
ApplicationDependencies.getJobManager().add(new UpdateApkJob());
}
long newTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setUpdateApkRefreshTime(context, newTime);
return newTime;
}
public static void schedule(Context context) {
new UpdateApkRefreshListener().onReceive(context, new Intent());
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.content.Context;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class VerificationCodeParser {
private static final Pattern CHALLENGE_PATTERN = Pattern.compile(".*Your (Signal|TextSecure) verification code:? ([0-9]{3,4})-([0-9]{3,4}).*", Pattern.DOTALL);
public static Optional<String> parse(Context context, String messageBody) {
if (messageBody == null) {
return Optional.absent();
}
Matcher challengeMatcher = CHALLENGE_PATTERN.matcher(messageBody);
if (!challengeMatcher.matches() || !TextSecurePreferences.isVerifying(context)) {
return Optional.absent();
}
return Optional.of(challengeMatcher.group(2) + challengeMatcher.group(3));
}
}

File diff suppressed because it is too large Load Diff