mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bc34daf35 | |||
| c47482851b | |||
| e2fb3968ca | |||
| ceffc5df1c | |||
| e6fd96bf35 | |||
| 60ab7f2826 | |||
| e511237e5e | |||
| f249fd6174 | |||
| 6db45c1911 | |||
| 06a1114744 | |||
| 3afff5625b | |||
| cef8ddeedb | |||
| 2f0ac973c0 | |||
| 9465776715 | |||
| 39685a575d | |||
| 8601736387 | |||
| 98d8572baf | |||
| 917e764b6f | |||
| 52263150c8 | |||
| c2aba94b34 | |||
| 1278dfd395 | |||
| 44a688470f | |||
| e9a09d9c72 | |||
| abc49a3b95 | |||
| cdd4e5eccd | |||
| fa8ed2b881 | |||
| 0731fe9447 | |||
| d3567d9f0c | |||
| 66ab1f8051 | |||
| 872fd17f5e | |||
| af49018911 | |||
| f870e9c8fd | |||
| d5982ba09f | |||
| 22d70d7bd4 | |||
| 09bdc32a9c | |||
| 7763ba17bf | |||
| 965204c46f | |||
| 815e4def6f | |||
| dc3ca8ff70 | |||
| 3bccb3fb84 | |||
| 402d93b5a4 | |||
| 927dc46431 | |||
| 87c32b1904 | |||
| bb97213460 | |||
| 9ca9bf1acd | |||
| 885c902b14 | |||
| 61bf5aaad4 | |||
| 5d76586ac1 | |||
| beb45af440 | |||
| 87a21eb0f2 | |||
| dea51cd356 | |||
| ed540e5584 | |||
| 9f80c9f35f | |||
| 5dec1b24cd |
+12
@@ -243,3 +243,15 @@ $ANDROID_NDK_ROOT/ndk-stack --sym obj/local/armeabi-v7a --dump crash.txt > decod
|
||||
`obj/local/armeabi-v7a` is the extracted path from `deltachat-gplay-release-X.X.X.apk-symbols.zip` file from https://download.delta.chat/android/symbols/
|
||||
|
||||
Replace `armeabi-v7a` by the correct architecture the logs come from (can be guessed by trial and error)
|
||||
|
||||
|
||||
### Deobfuscating Java Stack Traces
|
||||
|
||||
Because the app uses code minification (ProGuard/R8), Java stack traces in crash reports are obfuscated.
|
||||
To decode them, use `retrace` with the `mapping.txt` file that is included in the symbols zip:
|
||||
|
||||
```
|
||||
retrace mapping.txt crash.txt > decoded-crash.txt
|
||||
```
|
||||
|
||||
`mapping.txt` is extracted from the same `deltachat-gplay-release-X.X.X.apk-symbols.zip` file available at https://download.delta.chat/android/symbols/
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
* Better incoming call system integration
|
||||
* Calls are not experimental anymore and don't need to be manually enabled
|
||||
* Display a permanent notification when doing location streaming and get rid of dangerous "Access Location in Background" permission
|
||||
* Allow to share location for 24 hours
|
||||
* Allow mini-apps to play audio without user interaction
|
||||
* Mark chats as unread (long tap a chat and select the corresponding option from the three-dot-menu)
|
||||
* Fix process of upgrading from a very old version of the app
|
||||
|
||||
## v2.49.0
|
||||
2026-04
|
||||
|
||||
+2
-1
@@ -223,7 +223,7 @@ spotless {
|
||||
}
|
||||
format 'xml', {
|
||||
target 'src/*/res/**/*.xml', 'src/*/AndroidManifest.xml'
|
||||
targetExclude 'src/*/res/values*/strings.xml' // line-break changes invalidate translations
|
||||
targetExclude 'src/*/res/values*/strings*.xml' // line-break changes invalidate translations
|
||||
eclipseWtp('xml').configFile('spotless/eclipse-wtp-xml.prefs')
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
@@ -307,6 +307,7 @@ dependencies {
|
||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||
}
|
||||
gplayImplementation 'com.google.android.gms:play-services-location:21.3.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.assertj:assertj-core:3.27.3'
|
||||
|
||||
@@ -25,7 +25,7 @@ ArcaneChat is a Delta Chat client and was created with a focus on usability, goo
|
||||
<li>Multiple color themes/skins</li>
|
||||
<li>It is possible to disable profiles to completely disconnect them saving data/bandwidth</li>
|
||||
<li>You can easily see the connection status of all your profiles in the profile switcher</li>
|
||||
<li>Extra option to share location for 12 hours</li>
|
||||
<li>Extra option to share location for 24 hours</li>
|
||||
<li>Clicking on a message with a POI location, will open the POI on the map</li>
|
||||
<li>Last-seen status of contacts is shown in your contact list, like in WhatsApp, Telegram, etc.</li>
|
||||
<li>Videos are played in loop, useful for short GIF videos</li>
|
||||
|
||||
@@ -1393,6 +1393,7 @@ JNIEXPORT jint Java_com_b44t_messenger_DcMsg_getId(JNIEnv *env, jobject obj)
|
||||
return dc_msg_get_id(get_dc_msg(env, obj));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getText(JNIEnv *env, jobject obj)
|
||||
{
|
||||
char* temp = dc_msg_get_text(get_dc_msg(env, obj));
|
||||
|
||||
@@ -34,6 +34,7 @@ cd ../..
|
||||
SYMBOLS_ZIP="$APK-symbols.zip"
|
||||
rm $SYMBOLS_ZIP
|
||||
zip -r $SYMBOLS_ZIP obj
|
||||
zip $SYMBOLS_ZIP build/outputs/mapping/gplayRelease/mapping.txt
|
||||
ls -l $SYMBOLS_ZIP
|
||||
rsync --progress $SYMBOLS_ZIP jekyll@download.delta.chat:/var/www/html/download/android/symbols/
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public class EnterChatsBenchmark {
|
||||
// PLEASE BACKUP YOUR ACCOUNT BEFORE RUNNING THIS!
|
||||
// ==============================================================================================
|
||||
|
||||
private static final String TAG = EnterChatsBenchmark.class.getSimpleName();
|
||||
private static final String TAG = "EnterChatsBenchmark";
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<ConversationListActivity> activityRule =
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/** Non-GMS, always uses the platform LocationManager. */
|
||||
public final class LocationSourceFactory {
|
||||
|
||||
private static final String TAG = "LocationSourceFactory";
|
||||
|
||||
private LocationSourceFactory() {}
|
||||
|
||||
public static LocationSource create(Context context) {
|
||||
Log.i(TAG, "Non-GMS build, Using platform LocationManager");
|
||||
return new PlatformLocationSource();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.gms.location.FusedLocationProviderClient;
|
||||
import com.google.android.gms.location.LocationCallback;
|
||||
import com.google.android.gms.location.LocationRequest;
|
||||
import com.google.android.gms.location.LocationResult;
|
||||
import com.google.android.gms.location.LocationServices;
|
||||
import com.google.android.gms.location.Priority;
|
||||
|
||||
public class GmsLocationSource implements LocationSource {
|
||||
|
||||
private static final String TAG = "GmsLocationSource";
|
||||
private static final long UPDATE_INTERVAL_MS = 3_000;
|
||||
private static final long FASTEST_INTERVAL_MS = 1_000;
|
||||
|
||||
private FusedLocationProviderClient client;
|
||||
private LocationCallback locationCallback;
|
||||
|
||||
@Override
|
||||
public void startUpdates(@NonNull Context context, @NonNull Callback callback) {
|
||||
client = LocationServices.getFusedLocationProviderClient(context);
|
||||
|
||||
LocationRequest request =
|
||||
new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, UPDATE_INTERVAL_MS)
|
||||
.setMinUpdateIntervalMillis(FASTEST_INTERVAL_MS)
|
||||
.setMinUpdateDistanceMeters(0)
|
||||
.setWaitForAccurateLocation(false)
|
||||
.build();
|
||||
|
||||
locationCallback =
|
||||
new LocationCallback() {
|
||||
@Override
|
||||
public void onLocationResult(@NonNull LocationResult result) {
|
||||
Location loc = result.getLastLocation();
|
||||
if (loc != null) {
|
||||
callback.onLocationUpdate(loc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
client.requestLocationUpdates(request, locationCallback, Looper.getMainLooper());
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Missing location permission", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopUpdates() {
|
||||
if (client != null && locationCallback != null) {
|
||||
client.removeLocationUpdates(locationCallback);
|
||||
client = null;
|
||||
locationCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
|
||||
/**
|
||||
* Prefers FusedLocationProviderClient, falls back to platform LocationManager if Play Services are
|
||||
* somehow unavailable.
|
||||
*/
|
||||
public final class LocationSourceFactory {
|
||||
|
||||
private static final String TAG = "LocationSourceFactory";
|
||||
|
||||
private LocationSourceFactory() {}
|
||||
|
||||
public static LocationSource create(Context context) {
|
||||
if (isGmsAvailable(context)) {
|
||||
Log.i(TAG, "Using FusedLocationProviderClient");
|
||||
return new GmsLocationSource();
|
||||
}
|
||||
Log.i(TAG, "GMS unavailable, falling back to LocationManager");
|
||||
return new PlatformLocationSource();
|
||||
}
|
||||
|
||||
private static boolean isGmsAvailable(Context context) {
|
||||
try {
|
||||
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
|
||||
== ConnectionResult.SUCCESS;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.service.FetchForegroundService;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class FcmReceiveService extends FirebaseMessagingService {
|
||||
private static final String TAG = FcmReceiveService.class.getSimpleName();
|
||||
private static final String TAG = "FcmReceiveService";
|
||||
private static final Object INIT_LOCK = new Object();
|
||||
private static boolean initialized;
|
||||
private static volatile boolean triedRegistering;
|
||||
|
||||
@@ -71,6 +71,8 @@
|
||||
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
||||
|
||||
<!-- force compiling libs on older sdk than supported; runtime checks are required -->
|
||||
@@ -81,7 +83,6 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
tools:replace="android:allowBackup"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:largeHeap="true"
|
||||
@@ -464,10 +465,6 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".geolocation.LocationBackgroundService"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
<service
|
||||
android:name=".service.GenericForegroundService"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
@@ -476,6 +473,11 @@
|
||||
android:name=".service.FetchForegroundService"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<service
|
||||
android:name=".geolocation.LocationStreamingService"
|
||||
android:foregroundServiceType="location"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".service.AudioPlaybackService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
@@ -489,7 +491,7 @@
|
||||
android:name=".calls.CallService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="camera|microphone" />
|
||||
android:foregroundServiceType="camera|microphone|phoneCall" />
|
||||
|
||||
<receiver
|
||||
android:name=".notifications.MarkReadReceiver"
|
||||
|
||||
@@ -59,8 +59,6 @@ public class DcMsg {
|
||||
public static final int DC_VIDEOCHATTYPE_UNKNOWN = 0;
|
||||
public static final int DC_VIDEOCHATTYPE_BASICWEBRTC = 1;
|
||||
|
||||
private static final String TAG = DcMsg.class.getSimpleName();
|
||||
|
||||
public DcMsg(DcContext context, int viewtype) {
|
||||
msgCPtr = context.createMsgCPtr(viewtype);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class AllMediaActivity extends PassphraseRequiredActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = AllMediaActivity.class.getSimpleName();
|
||||
private static final String TAG = "AllMediaActivity";
|
||||
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.connect.KeepAliveService;
|
||||
import org.thoughtcrime.securesms.connect.NetworkStateReceiver;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.notifications.FcmReceiveService;
|
||||
import org.thoughtcrime.securesms.notifications.InChatSounds;
|
||||
@@ -53,7 +52,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.webxdc.WebxdcGarbageCollectionWorker;
|
||||
|
||||
public class ApplicationContext extends MultiDexApplication {
|
||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||
private static final String TAG = "ApplicationContext";
|
||||
private static final Object initLock = new Object();
|
||||
private static volatile boolean isInitialized = false;
|
||||
|
||||
@@ -61,7 +60,6 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
private Rpc rpc;
|
||||
private DcContext dcContext;
|
||||
|
||||
private DcLocationManager dcLocationManager;
|
||||
private DcEventCenter eventCenter;
|
||||
private NotificationCenter notificationCenter;
|
||||
private JobManager jobManager;
|
||||
@@ -126,15 +124,6 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DcLocationManager instance, waiting for initialization if necessary. This method is
|
||||
* thread-safe and will block until initialization is complete.
|
||||
*/
|
||||
public DcLocationManager getLocationManager() {
|
||||
ensureInitialized();
|
||||
return dcLocationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DcEventCenter instance, waiting for initialization if necessary. This method is thread-safe
|
||||
* and will block until initialization is complete.
|
||||
@@ -229,7 +218,7 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
Log.i(TAG, "DcAccounts created");
|
||||
rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance()));
|
||||
Log.i(TAG, "Rpc created");
|
||||
AccountManager.getInstance().migrateToDcAccounts(this);
|
||||
AccountManager.getInstance().migrateToDcAccounts(this, dcAccounts);
|
||||
|
||||
int[] allAccounts = dcAccounts.getAll();
|
||||
Log.i(TAG, "Number of profiles: " + allAccounts.length);
|
||||
@@ -282,7 +271,6 @@ public class ApplicationContext extends MultiDexApplication {
|
||||
}
|
||||
dcContext = dcAccounts.getSelectedAccount();
|
||||
notificationCenter = new NotificationCenter(this);
|
||||
dcLocationManager = new DcLocationManager(this, dcContext);
|
||||
|
||||
isInitialized = true;
|
||||
initLock.notifyAll();
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
|
||||
private static final String TAG = "BaseActionBarActivity";
|
||||
protected DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
|
||||
protected void onPreCreate() {
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -26,6 +27,8 @@ import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import chat.delta.rpc.Rpc;
|
||||
import chat.delta.rpc.RpcException;
|
||||
import com.b44t.messenger.DcChat;
|
||||
import com.b44t.messenger.DcContact;
|
||||
import com.b44t.messenger.DcContext;
|
||||
@@ -44,6 +47,7 @@ import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
|
||||
public abstract class BaseConversationListFragment extends Fragment implements ActionMode.Callback {
|
||||
private static final String TAG = "BaseConversationListFragment";
|
||||
protected ActionMode actionMode;
|
||||
protected PulsingFloatingActionButton fab;
|
||||
|
||||
@@ -118,9 +122,6 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
if (SendRelayedMessageUtil.containsVideoType(context, uris)) {
|
||||
message += "\n\n" + getString(R.string.videos_sent_without_recoding);
|
||||
}
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setCancelable(false)
|
||||
@@ -128,11 +129,16 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
.setPositiveButton(
|
||||
R.string.menu_send,
|
||||
(dialog, which) -> {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
SendRelayedMessageUtil.immediatelyRelay(
|
||||
getActivity(), selectedChats.toArray(new Long[selectedChats.size()]));
|
||||
activity,
|
||||
selectedChats.toArray(new Long[selectedChats.size()]),
|
||||
() -> activity.finish());
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
getActivity().finish();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
@@ -170,6 +176,17 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean areSomeSelectedChatsFresh() {
|
||||
DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedChats = getListAdapter().getBatchSelections();
|
||||
for (long chatId : selectedChats) {
|
||||
if (dcContext.getFreshMsgCount((int) chatId) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handlePinAllSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
final Set<Long> selectedConversations =
|
||||
@@ -229,6 +246,26 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMarkfreshSelected() {
|
||||
final Set<Long> selectedConversations =
|
||||
new HashSet<Long>(getListAdapter().getBatchSelections());
|
||||
|
||||
final Rpc rpc = DcHelper.getRpc(requireActivity());
|
||||
try {
|
||||
int accId = rpc.getSelectedAccountId();
|
||||
for (long chatId : selectedConversations) {
|
||||
rpc.markfreshChat(accId, (int) chatId);
|
||||
}
|
||||
} catch (RpcException e) {
|
||||
Log.e(TAG, "RPC error", e);
|
||||
}
|
||||
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void handleArchiveAllSelected() {
|
||||
final DcContext dcContext = DcHelper.getContext(requireActivity());
|
||||
@@ -409,6 +446,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
if (!isRelayingMessageContent(requireActivity())) {
|
||||
final int selectedCount = getListAdapter().getBatchSelections().size();
|
||||
menu.findItem(R.id.menu_add_to_home_screen).setVisible(selectedCount == 1);
|
||||
|
||||
MenuItem archiveItem = menu.findItem(R.id.menu_archive_selected);
|
||||
if (offerToArchive()) {
|
||||
archiveItem.setIcon(R.drawable.ic_archive_white_24dp);
|
||||
@@ -417,6 +455,7 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
archiveItem.setIcon(R.drawable.ic_unarchive_white_24dp);
|
||||
archiveItem.setTitle(R.string.menu_unarchive_chat);
|
||||
}
|
||||
|
||||
MenuItem pinItem = menu.findItem(R.id.menu_pin_selected);
|
||||
if (areSomeSelectedChatsUnpinned()) {
|
||||
pinItem.setIcon(R.drawable.ic_pin_white);
|
||||
@@ -425,12 +464,17 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
pinItem.setIcon(R.drawable.ic_unpin_white);
|
||||
pinItem.setTitle(R.string.unpin_chat);
|
||||
}
|
||||
|
||||
MenuItem muteItem = menu.findItem(R.id.menu_mute_selected);
|
||||
if (areSomeSelectedChatsUnmuted()) {
|
||||
muteItem.setTitle(R.string.menu_mute);
|
||||
} else {
|
||||
muteItem.setTitle(R.string.menu_unmute);
|
||||
}
|
||||
|
||||
final boolean someAreFresh = areSomeSelectedChatsFresh();
|
||||
menu.findItem(R.id.menu_marknoticed_selected).setVisible(someAreFresh);
|
||||
menu.findItem(R.id.menu_markfresh_selected).setVisible(!someAreFresh);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +528,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A
|
||||
} else if (itemId == R.id.menu_marknoticed_selected) {
|
||||
handleMarknoticedSelected();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_markfresh_selected) {
|
||||
handleMarkfreshSelected();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_add_to_home_screen) {
|
||||
handleAddToHomeScreen();
|
||||
return true;
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
*/
|
||||
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
||||
private static final String TAG = "ContactSelectionActivity";
|
||||
|
||||
protected ContactSelectionListFragment contactsFragment;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
*/
|
||||
public class ContactSelectionListFragment extends Fragment
|
||||
implements LoaderManager.LoaderCallbacks<DcContactsLoader.Ret>, DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
|
||||
private static final String TAG = "ContactSelectionListFragment";
|
||||
|
||||
public static final String MULTI_SELECT = "multi_select";
|
||||
public static final String SELECT_UNENCRYPTED_EXTRA = "select_unencrypted_extra";
|
||||
|
||||
@@ -148,7 +148,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
InputPanel.Listener,
|
||||
InputPanel.MediaListener,
|
||||
AudioView.OnActionListener {
|
||||
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||
private static final String TAG = "ConversationActivity";
|
||||
|
||||
public static final String ACCOUNT_ID_EXTRA = "account_id";
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
@@ -860,9 +860,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private void askSendingFiles(ArrayList<Uri> uriList, Runnable onConfirm) {
|
||||
String message =
|
||||
String.format(getString(R.string.ask_send_files_to_chat), uriList.size(), dcChat.getName());
|
||||
if (SendRelayedMessageUtil.containsVideoType(context, uriList)) {
|
||||
message += "\n\n" + getString(R.string.videos_sent_without_recoding);
|
||||
}
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(message)
|
||||
.setCancelable(false)
|
||||
|
||||
@@ -80,7 +80,7 @@ import org.thoughtcrime.securesms.util.views.ConversationAdaptiveActionsToolbar;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class ConversationFragment extends MessageSelectorFragment {
|
||||
private static final String TAG = ConversationFragment.class.getSimpleName();
|
||||
private static final String TAG = "ConversationFragment";
|
||||
|
||||
private static final int SCROLL_ANIMATION_THRESHOLD = 50;
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ import org.thoughtcrime.securesms.util.views.Stub;
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ConversationItem extends BaseConversationItem {
|
||||
private static final String TAG = ConversationItem.class.getSimpleName();
|
||||
private static final String TAG = "ConversationItem";
|
||||
|
||||
private static final Rect SWIPE_RECT = new Rect();
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
||||
import org.thoughtcrime.securesms.geolocation.LocationStreamingService;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
@@ -90,7 +91,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ConversationListFragment.ConversationSelectedListener {
|
||||
private static final String TAG = ConversationListActivity.class.getSimpleName();
|
||||
private static final String TAG = "ConversationListActivity";
|
||||
private static final String OPENPGP4FPR = "openpgp4fpr";
|
||||
private static final String NDK_ARCH_WARNED = "ndk_arch_warned";
|
||||
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
|
||||
@@ -449,6 +450,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
refreshTitle();
|
||||
invalidateOptionsMenu();
|
||||
DirectShareUtil.triggerRefreshDirectShare(this);
|
||||
if (DcHelper.getContext(this).isSendingLocationsToChat(0)) {
|
||||
LocationStreamingService.ensureRunning(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -622,14 +626,18 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
final DcContext dcContext = DcHelper.getContext(this);
|
||||
int fwdAccId = getForwardedMessageAccountId(this);
|
||||
if (fwdAccId == dcContext.getAccountId() && dcContext.getChat(chatId).isSelfTalk()) {
|
||||
SendRelayedMessageUtil.immediatelyRelay(this, chatId);
|
||||
Toast.makeText(
|
||||
this,
|
||||
DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
handleResetRelaying();
|
||||
finish();
|
||||
SendRelayedMessageUtil.immediatelyRelay(
|
||||
this,
|
||||
chatId,
|
||||
() -> {
|
||||
Toast.makeText(
|
||||
this,
|
||||
DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
handleResetRelaying();
|
||||
finish();
|
||||
});
|
||||
} else {
|
||||
Intent intent = new Intent(this, ConversationActivity.class);
|
||||
intent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, dcContext.getAccountId());
|
||||
@@ -688,7 +696,11 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
// Util.copy(inputStream, new FileOutputStream(outputFile));
|
||||
// msg.setFile(outputFile, "image/jpeg");
|
||||
|
||||
msg.setText(getString(R.string.update_2_46ac, "https://arcanechat.me/#contribute", "https://i.delta.chat/#0A45953086F0C166D3BAF1D4BB2025496E4C2704&x=MVPi07rQBEmHO4FRb3brpwDe&j=n8mkKqu42WAKKUCx1bQOVh23&s=AM1YCd_2hKuiSiTEb-2177On&a=arcanechat%40arcanechat.me&n=ArcaneChat&b=ArcaneChat+Channel"));
|
||||
msg.setText(
|
||||
getString(
|
||||
R.string.update_2_46ac,
|
||||
"https://arcanechat.me/#contribute",
|
||||
"https://i.delta.chat/#0A45953086F0C166D3BAF1D4BB2025496E4C2704&x=MVPi07rQBEmHO4FRb3brpwDe&j=n8mkKqu42WAKKUCx1bQOVh23&s=AM1YCd_2hKuiSiTEb-2177On&a=arcanechat%40arcanechat.me&n=ArcaneChat&b=ArcaneChat+Channel"));
|
||||
}
|
||||
dcContext.addDeviceMsg(deviceMsgLabel, msg);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ConversationListFragment extends BaseConversationListFragment
|
||||
public static final String ARCHIVE = "archive";
|
||||
public static final String RELOAD_LIST = "reload_list";
|
||||
|
||||
private static final String TAG = ConversationListFragment.class.getSimpleName();
|
||||
private static final String TAG = "ConversationListFragment";
|
||||
|
||||
private RecyclerView list;
|
||||
private View emptyState;
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class CreateProfileActivity extends BaseActionBarActivity {
|
||||
|
||||
private static final String TAG = CreateProfileActivity.class.getSimpleName();
|
||||
private static final String TAG = "CreateProfileActivity";
|
||||
|
||||
private static final int REQUEST_CODE_AVATAR = 1;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class EphemeralMessagesDialog {
|
||||
|
||||
private static final String TAG = EphemeralMessagesDialog.class.getSimpleName();
|
||||
private static final String TAG = "EphemeralMessagesDialog";
|
||||
|
||||
public static void show(
|
||||
final Context context,
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ItemClickListener {
|
||||
|
||||
private static final String TAG = GroupCreateActivity.class.getSimpleName();
|
||||
private static final String TAG = "GroupCreateActivity";
|
||||
public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id";
|
||||
public static final String CREATE_BROADCAST = "create_broadcast";
|
||||
public static final String UNENCRYPTED = "unencrypted";
|
||||
@@ -307,8 +307,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private boolean showGroupNameEmptyToast(String groupName) {
|
||||
if (groupName == null) {
|
||||
Toast.makeText(this, getString(R.string.group_please_enter_group_name), Toast.LENGTH_LONG)
|
||||
.show();
|
||||
Toast.makeText(this, getString(R.string.please_enter_chat_name), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -65,7 +65,7 @@ import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
public class InstantOnboardingActivity extends BaseActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = InstantOnboardingActivity.class.getSimpleName();
|
||||
private static final String TAG = "InstantOnboardingActivity";
|
||||
private static final String DCACCOUNT = "dcaccount";
|
||||
private static final String DCLOGIN = "dclogin";
|
||||
private static final String INSTANCES_URL = "https://chatmail.at/relays";
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.util.FileProviderUtil;
|
||||
|
||||
public class LogViewActivity extends BaseActionBarActivity {
|
||||
|
||||
private static final String TAG = LogViewActivity.class.getSimpleName();
|
||||
private static final String TAG = "LogViewActivity";
|
||||
|
||||
LogViewFragment logViewFragment;
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
|
||||
implements RecipientModifiedListener, LoaderManager.LoaderCallbacks<DcMediaGalleryElement> {
|
||||
|
||||
private static final String TAG = MediaPreviewActivity.class.getSimpleName();
|
||||
private static final String TAG = "MediaPreviewActivity";
|
||||
|
||||
public static final String ACTIVITY_TITLE_EXTRA = "activity_title";
|
||||
public static final String EDIT_AVATAR_CHAT_ID = "avatar_for_chat_id";
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.qr.QrCodeHandler;
|
||||
*/
|
||||
public class NewConversationActivity extends ContactSelectionActivity {
|
||||
|
||||
private static final String TAG = NewConversationActivity.class.getSimpleName();
|
||||
private static final String TAG = "NewConversationActivity";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, boolean ready) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
|
||||
public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity {
|
||||
private static final String TAG = PassphraseRequiredActionBarActivity.class.getSimpleName();
|
||||
private static final String TAG = "PassphraseRequiredActionBarActivity";
|
||||
|
||||
@Override
|
||||
protected final void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class ProfileAdapter extends RecyclerView.Adapter {
|
||||
private static final String TAG = ProfileAdapter.class.getSimpleName();
|
||||
private static final String TAG = "ProfileAdapter";
|
||||
|
||||
public static final int ITEM_AVATAR = 10;
|
||||
public static final int ITEM_DIVIDER = 20;
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
public class ProfileFragment extends Fragment
|
||||
implements ProfileAdapter.ItemClickListener, DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = ProfileFragment.class.getSimpleName();
|
||||
private static final String TAG = "ProfileFragment";
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
public static final String CONTACT_ID_EXTRA = "contact_id";
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
|
||||
public class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
|
||||
|
||||
private static final String TAG = ResolveMediaTask.class.getSimpleName();
|
||||
private static final String TAG = "ResolveMediaTask";
|
||||
|
||||
interface OnMediaResolvedListener {
|
||||
void onMediaResolved(Uri uri);
|
||||
|
||||
@@ -47,7 +47,7 @@ import org.thoughtcrime.securesms.util.ShareUtil;
|
||||
*/
|
||||
public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ResolveMediaTask.OnMediaResolvedListener {
|
||||
private static final String TAG = ShareActivity.class.getSimpleName();
|
||||
private static final String TAG = "ShareActivity";
|
||||
|
||||
public static final String EXTRA_ACC_ID = "acc_id";
|
||||
public static final String EXTRA_CHAT_ID = "chat_id";
|
||||
|
||||
@@ -18,22 +18,19 @@ public class ShareLocationDialog {
|
||||
switch (which) {
|
||||
default:
|
||||
case 0:
|
||||
shareLocationUnit = 5 * 60;
|
||||
break;
|
||||
case 1:
|
||||
shareLocationUnit = 30 * 60;
|
||||
break;
|
||||
case 2:
|
||||
case 1:
|
||||
shareLocationUnit = 60 * 60;
|
||||
break;
|
||||
case 3:
|
||||
case 2:
|
||||
shareLocationUnit = 2 * 60 * 60;
|
||||
break;
|
||||
case 4:
|
||||
case 3:
|
||||
shareLocationUnit = 6 * 60 * 60;
|
||||
break;
|
||||
case 5:
|
||||
shareLocationUnit = 12 * 60 * 60;
|
||||
case 4:
|
||||
shareLocationUnit = 24 * 60 * 60;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||
implements SearchView.OnQueryTextListener, WebView.FindListener {
|
||||
private static final String TAG = WebViewActivity.class.getSimpleName();
|
||||
private static final String TAG = "WebViewActivity";
|
||||
// Regex to extract the host from a URL for IDN conversion.
|
||||
private static final Pattern URL_PATTERN =
|
||||
Pattern.compile("^((?:[a-zA-Z0-9]+://)?)([^/?#]+)(.*)$");
|
||||
|
||||
@@ -59,7 +59,7 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = WebxdcActivity.class.getSimpleName();
|
||||
private static final String TAG = "WebxdcActivity";
|
||||
private static final String EXTRA_ACCOUNT_ID = "accountId";
|
||||
private static final String EXTRA_APP_MSG_ID = "appMessageId";
|
||||
private static final String EXTRA_HIDE_ACTION_BAR = "hideActionBar";
|
||||
@@ -240,6 +240,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||
|
||||
WebSettings webSettings = webView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
webSettings.setMediaPlaybackRequiresUserGesture(false);
|
||||
webSettings.setAllowFileAccess(false);
|
||||
webSettings.setBlockNetworkLoads(!internetAccess);
|
||||
webSettings.setAllowContentAccess(false);
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity {
|
||||
private static final String TAG = WebxdcStoreActivity.class.getSimpleName();
|
||||
private static final String TAG = "WebxdcStoreActivity";
|
||||
|
||||
private DcContext dcContext;
|
||||
private Rpc rpc;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class WelcomeActivity extends BaseActionBarActivity
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
public static final String BACKUP_QR_EXTRA = "backup_qr_extra";
|
||||
public static final int PICK_BACKUP = 20574;
|
||||
private static final String TAG = WelcomeActivity.class.getSimpleName();
|
||||
private static final String TAG = "WelcomeActivity";
|
||||
public static final String TMP_BACKUP_FILE = "tmp-backup-file";
|
||||
|
||||
private ProgressDialog progressDialog = null;
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class AccountSelectionListFragment extends DialogFragment
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = AccountSelectionListFragment.class.getSimpleName();
|
||||
private static final String TAG = "AccountSelectionListFragment";
|
||||
private static final String ARG_SELECT_ONLY = "select_only";
|
||||
private RecyclerView recyclerView;
|
||||
private AccountSelectionListAdapter adapter;
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||
|
||||
public class AudioCodec {
|
||||
|
||||
private static final String TAG = AudioCodec.class.getSimpleName();
|
||||
private static final String TAG = "AudioCodec";
|
||||
|
||||
private static final int SAMPLE_RATE_BALANCED = 44100;
|
||||
private static final int BIT_RATE_BALANCED = 32000;
|
||||
@@ -196,7 +196,7 @@ public class AudioCodec {
|
||||
setFinished();
|
||||
}
|
||||
},
|
||||
AudioCodec.class.getSimpleName())
|
||||
"AudioCodec")
|
||||
.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class AudioRecorder {
|
||||
|
||||
private static final String TAG = AudioRecorder.class.getSimpleName();
|
||||
private static final String TAG = "AudioRecorder";
|
||||
|
||||
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.R;
|
||||
/** Bottom sheet dialog for selecting audio output device */
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
public class AudioDevicePickerDialog extends BottomSheetDialog {
|
||||
private static final String TAG = AudioDevicePickerDialog.class.getSimpleName();
|
||||
private static final String TAG = "AudioDevicePickerDialog";
|
||||
|
||||
public interface OnDeviceSelectedListener {
|
||||
void onDeviceSelected(CallEndpointCompat endpoint);
|
||||
|
||||
@@ -51,7 +51,7 @@ import org.webrtc.VideoTrack;
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class CallActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = CallActivity.class.getSimpleName();
|
||||
private static final String TAG = "CallActivity";
|
||||
private static final int MIC_PERMISSION_REQUEST_CODE = 1001;
|
||||
private static final int CAMERA_PERMISSION_REQUEST_CODE = 1002;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ import org.webrtc.VideoTrack;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = CallCoordinator.class.getSimpleName();
|
||||
private static final String TAG = "CallCoordinator";
|
||||
|
||||
// Notification channels
|
||||
private static final String CHANNEL_ID_INCOMING = "voip_incoming_calls";
|
||||
@@ -215,7 +215,8 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
calleeName = "Unknown";
|
||||
}
|
||||
|
||||
showOrUpdateOngoingNotification("Calling " + calleeName + "...");
|
||||
showOrUpdateOngoingNotification(
|
||||
appContext.getString(R.string.calling_person, calleeName));
|
||||
}
|
||||
|
||||
// Initialize call
|
||||
@@ -402,6 +403,15 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
callService.stopRingtone();
|
||||
}
|
||||
|
||||
// Promote the service to foreground immediately. Waiting until onIncomingCallAccepted
|
||||
// on executor pool thread is too late on stricter OEM.
|
||||
//
|
||||
// Do not cancel() but use showOrUpdateOngoingNotification to replace incoming
|
||||
// notification without a gap.
|
||||
String callerName = displayName.getValue();
|
||||
if (callerName == null) callerName = "Unknown";
|
||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, callerName));
|
||||
|
||||
// Notify Android system with CallControlScope
|
||||
CallControlScope scope = activeCallControlScope;
|
||||
if (scope != null) {
|
||||
@@ -425,8 +435,6 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
notificationManager.cancel(NOTIFICATION_ID_CALL);
|
||||
}
|
||||
|
||||
public void answerWebRTC() {
|
||||
@@ -921,7 +929,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
callerName = "Unknown";
|
||||
}
|
||||
|
||||
showOrUpdateOngoingNotification("Call with " + callerName);
|
||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, callerName));
|
||||
}
|
||||
|
||||
private synchronized void onCallAnsweredOnOtherDevice() {
|
||||
@@ -1008,7 +1016,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
if (calleeName == null) {
|
||||
calleeName = "Unknown";
|
||||
}
|
||||
showOrUpdateOngoingNotification("Call with " + calleeName);
|
||||
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, calleeName));
|
||||
}
|
||||
|
||||
private synchronized void onCallEnded(int accId, int callId) {
|
||||
@@ -1101,6 +1109,14 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callService != null) {
|
||||
try {
|
||||
callService.stopForegroundAndDismiss();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "stopForegroundAndDismiss failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear state
|
||||
this.activeAccId = null;
|
||||
this.activeCallId = null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioManager;
|
||||
@@ -13,7 +14,9 @@ import android.media.ToneGenerator;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
@@ -33,7 +36,7 @@ import org.webrtc.VideoTrack;
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
|
||||
private static final String TAG = CallService.class.getSimpleName();
|
||||
private static final String TAG = "CallService";
|
||||
|
||||
private final IBinder binder = new LocalBinder();
|
||||
|
||||
@@ -432,6 +435,12 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
|
||||
disposeWebRTC();
|
||||
|
||||
try {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "stopForeground failed", e);
|
||||
}
|
||||
|
||||
stopService();
|
||||
}
|
||||
|
||||
@@ -494,13 +503,44 @@ public class CallService extends Service implements WebRTCClient.Callbacks {
|
||||
// Foreground Notification
|
||||
|
||||
public void startForegroundWithNotification(int id, Notification notification) {
|
||||
Log.d(TAG, "Starting foreground with notification id: " + id);
|
||||
startForeground(id, notification);
|
||||
// Always run on main thread
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
new Handler(Looper.getMainLooper())
|
||||
.post(() -> startForegroundWithNotification(id, notification));
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Starting call FGS with notification id: " + id);
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
int types =
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
||||
| ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
|
||||
startForeground(id, notification, types);
|
||||
} else {
|
||||
startForeground(id, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "startForeground failed", e);
|
||||
if (callCoordinator != null) {
|
||||
callCoordinator.reportError("Failed to activate call FGS: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stopForegroundAndDismiss() {
|
||||
Log.d(TAG, "Stopping foreground and dismissing notification");
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
new Handler(Looper.getMainLooper()).post(this::stopForegroundAndDismiss);
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Stopping call FGS and dismissing notification");
|
||||
try {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "stopForeground failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
|
||||
public class CallUtil {
|
||||
private static final String TAG = CallUtil.class.getSimpleName();
|
||||
private static final String TAG = "CallUtil";
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void startAudioCall(Context context, int chatId) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.webrtc.VideoTrack;
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class CallViewModel extends AndroidViewModel {
|
||||
|
||||
private static final String TAG = CallViewModel.class.getSimpleName();
|
||||
private static final String TAG = "CallViewModel";
|
||||
|
||||
private final CallCoordinator callCoordinator;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.webrtc.VideoTrack;
|
||||
|
||||
public class MediaStreamManager {
|
||||
|
||||
private static final String TAG = MediaStreamManager.class.getSimpleName();
|
||||
private static final String TAG = "MediaStreamManager";
|
||||
private static final String STREAM_ID = "local_stream";
|
||||
private static final String AUDIO_TRACK_ID = "audio_track";
|
||||
private static final String VIDEO_TRACK_ID = "video_track";
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.loader.app.LoaderManager;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class AttachmentTypeSelector extends PopupWindow {
|
||||
@@ -45,7 +46,7 @@ public class AttachmentTypeSelector extends PopupWindow {
|
||||
private final @NonNull ImageView imageButton;
|
||||
private final @NonNull ImageView documentButton;
|
||||
private final @NonNull ImageView contactButton;
|
||||
// private final @NonNull ImageView cameraButton;
|
||||
// private final @NonNull ImageView cameraButton;
|
||||
private final @NonNull ImageView videoButton;
|
||||
private final @NonNull ImageView locationButton;
|
||||
private final @NonNull ImageView webxdcButton;
|
||||
@@ -87,11 +88,9 @@ public class AttachmentTypeSelector extends PopupWindow {
|
||||
this.webxdcButton.setOnClickListener(new PropagatingClickListener(ADD_WEBXDC));
|
||||
this.recentRail.setListener(new RecentPhotoSelectedListener());
|
||||
|
||||
// disable location streaming button for now
|
||||
// if (!Prefs.isLocationStreamingEnabled(context)) {
|
||||
this.locationButton.setVisibility(View.GONE);
|
||||
ViewUtil.findById(layout, R.id.location_button_label).setVisibility(View.GONE);
|
||||
// }
|
||||
if (!Prefs.isLocationStreamingEnabled(context)) {
|
||||
ViewUtil.findById(layout, R.id.location_linear_layout).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setLocationButtonImage(context);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
public class CallItemView extends FrameLayout {
|
||||
private static final String TAG = CallItemView.class.getSimpleName();
|
||||
private static final String TAG = "CallItemView";
|
||||
|
||||
private final @NonNull ImageView icon;
|
||||
private final @NonNull TextView title;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class InputPanel extends ConstraintLayout
|
||||
KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
||||
MediaKeyboard.MediaKeyboardListener {
|
||||
|
||||
private static final String TAG = InputPanel.class.getSimpleName();
|
||||
private static final String TAG = "InputPanel";
|
||||
|
||||
private static final int FADE_TIME = 150;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
* been opened and what its height would be.
|
||||
*/
|
||||
public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
|
||||
private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName();
|
||||
private static final String TAG = "KeyboardAwareLinearLayout";
|
||||
|
||||
private static final long KEYBOARD_DEBOUNCE = 150;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class QuoteView extends FrameLayout implements RecipientForeverObserver {
|
||||
|
||||
private static final String TAG = QuoteView.class.getSimpleName();
|
||||
private static final String TAG = "QuoteView";
|
||||
|
||||
private static final int MESSAGE_TYPE_PREVIEW = 0;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public class ScaleStableImageView extends AppCompatImageView
|
||||
implements KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
||||
KeyboardAwareLinearLayout.OnKeyboardHiddenListener {
|
||||
|
||||
private static final String TAG = ScaleStableImageView.class.getSimpleName();
|
||||
private static final String TAG = "ScaleStableImageView";
|
||||
|
||||
private Drawable defaultDrawable;
|
||||
private Drawable currentDrawable;
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
|
||||
public class SearchToolbar extends LinearLayout {
|
||||
|
||||
private static final String TAG = SearchToolbar.class.getSimpleName();
|
||||
private static final String TAG = "SearchToolbar";
|
||||
private float x, y;
|
||||
private MenuItem searchItem;
|
||||
private EditText searchText;
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class ThumbnailView extends FrameLayout {
|
||||
|
||||
private static final String TAG = ThumbnailView.class.getSimpleName();
|
||||
private static final String TAG = "ThumbnailView";
|
||||
private static final int WIDTH = 0;
|
||||
private static final int HEIGHT = 1;
|
||||
private static final int MIN_WIDTH = 0;
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.mms.VcardSlide;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
public class VcardView extends FrameLayout {
|
||||
private static final String TAG = VcardView.class.getSimpleName();
|
||||
private static final String TAG = "VcardView";
|
||||
|
||||
private final @NonNull AvatarView avatar;
|
||||
private final @NonNull TextView name;
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class AudioPlaybackViewModel extends ViewModel {
|
||||
private static final String TAG = AudioPlaybackViewModel.class.getSimpleName();
|
||||
private static final String TAG = "AudioPlaybackViewModel";
|
||||
|
||||
private static final int NON_MESSAGE_AUDIO_MSG_ID =
|
||||
0; // Audios not attached to a message doesn't have message id.
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
public class AudioView extends FrameLayout {
|
||||
|
||||
private static final String TAG = AudioView.class.getSimpleName();
|
||||
private static final String TAG = "AudioView";
|
||||
|
||||
private final @NonNull ImageView playPauseButton;
|
||||
private final AnimatedVectorDrawableCompat playToPauseDrawable;
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.util.ResUtil;
|
||||
public class MediaKeyboard extends LinearLayout
|
||||
implements InputView, Consumer<EmojiViewItem>, StickerPickerView.StickerPickerListener {
|
||||
|
||||
private static final String TAG = MediaKeyboard.class.getSimpleName();
|
||||
private static final String TAG = "MediaKeyboard";
|
||||
|
||||
@Nullable private MediaKeyboardListener keyboardListener;
|
||||
private EmojiPickerView emojiPicker;
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
public class DozeReminder {
|
||||
private static final String TAG = DozeReminder.class.getSimpleName();
|
||||
private static final String TAG = "DozeReminder";
|
||||
|
||||
public static boolean isEligible(Context context) {
|
||||
if (context == null) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import androidx.viewpager.widget.ViewPager;
|
||||
*/
|
||||
public class HackyViewPager extends ViewPager {
|
||||
|
||||
private static final String TAG = HackyViewPager.class.getSimpleName();
|
||||
private static final String TAG = "HackyViewPager";
|
||||
|
||||
public HackyViewPager(Context context) {
|
||||
super(context);
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.accounts.AccountSelectionListFragment;
|
||||
|
||||
public class AccountManager {
|
||||
|
||||
private static final String TAG = AccountManager.class.getSimpleName();
|
||||
private static final String TAG = "AccountManager";
|
||||
private static final String LAST_ACCOUNT_ID = "last_account_id";
|
||||
private static AccountManager self;
|
||||
|
||||
@@ -38,7 +38,7 @@ public class AccountManager {
|
||||
return self;
|
||||
}
|
||||
|
||||
public void migrateToDcAccounts(ApplicationContext context) {
|
||||
public void migrateToDcAccounts(Context context, DcAccounts dcAccounts) {
|
||||
try {
|
||||
int selectAccountId = 0;
|
||||
|
||||
@@ -49,8 +49,7 @@ public class AccountManager {
|
||||
if (!file.isDirectory()
|
||||
&& file.getName().startsWith("messenger")
|
||||
&& file.getName().endsWith(".db")) {
|
||||
int accountId =
|
||||
ApplicationContext.getDcAccounts().migrateAccount(file.getAbsolutePath());
|
||||
int accountId = dcAccounts.migrateAccount(file.getAbsolutePath());
|
||||
if (accountId != 0) {
|
||||
String selName =
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
@@ -67,7 +66,7 @@ public class AccountManager {
|
||||
}
|
||||
|
||||
if (selectAccountId != 0) {
|
||||
ApplicationContext.getDcAccounts().selectAccount(selectAccountId);
|
||||
dcAccounts.selectAccount(selectAccountId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in migrateToDcAccounts()", e);
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.service.FetchForegroundService;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class DcEventCenter {
|
||||
private static final String TAG = DcEventCenter.class.getSimpleName();
|
||||
private static final String TAG = "DcEventCenter";
|
||||
private @NonNull final Hashtable<Integer, ArrayList<DcEventDelegate>> currentAccountObservers =
|
||||
new Hashtable<>();
|
||||
private @NonNull final Hashtable<Integer, ArrayList<DcEventDelegate>> multiAccountObservers =
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class DcHelper {
|
||||
|
||||
private static final String TAG = DcHelper.class.getSimpleName();
|
||||
private static final String TAG = "DcHelper";
|
||||
|
||||
public static final String CONFIG_CONFIGURED_ADDRESS = "configured_addr";
|
||||
public static final String CONFIG_DISPLAY_NAME = "displayname";
|
||||
@@ -225,6 +225,8 @@ public class DcHelper {
|
||||
dcContext.setStockTranslation(201, context.getString(R.string.qrshow_join_channel_hint));
|
||||
dcContext.setStockTranslation(202, context.getString(R.string.you_joined_the_channel));
|
||||
dcContext.setStockTranslation(203, context.getString(R.string.secure_join_channel_started));
|
||||
dcContext.setStockTranslation(204, context.getString(R.string.channel_name_changed));
|
||||
dcContext.setStockTranslation(205, context.getString(R.string.channel_image_changed));
|
||||
dcContext.setStockTranslation(210, context.getString(R.string.stats_msg_body));
|
||||
dcContext.setStockTranslation(220, context.getString(R.string.proxy_enabled));
|
||||
dcContext.setStockTranslation(221, context.getString(R.string.proxy_enabled_hint));
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
*/
|
||||
public class DirectShareUtil {
|
||||
|
||||
private static final String TAG = DirectShareUtil.class.getSimpleName();
|
||||
private static final String TAG = "DirectShareUtil";
|
||||
private static final String SHORTCUT_CATEGORY = "android.shortcut.conversation";
|
||||
|
||||
public static void clearShortcut(@NonNull Context context, int chatId) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.connect;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -11,6 +10,7 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||
|
||||
public class KeepAliveService extends Service {
|
||||
|
||||
private static final String TAG = KeepAliveService.class.getSimpleName();
|
||||
private static final String TAG = "KeepAliveService";
|
||||
|
||||
static KeepAliveService s_this = null;
|
||||
|
||||
@@ -119,7 +119,7 @@ public class KeepAliveService extends Service {
|
||||
|
||||
private static boolean ch_created = false;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private static void createFgNotificationChannel(Context context) {
|
||||
if (!ch_created) {
|
||||
ch_created = true;
|
||||
|
||||
@@ -10,7 +10,7 @@ import android.util.Log;
|
||||
|
||||
public class NetworkStateReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = NetworkStateReceiver.class.getSimpleName();
|
||||
private static final String TAG = "NetworkStateReceiver";
|
||||
private int debugConnectedCount;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.util.Hash;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
|
||||
@@ -37,7 +36,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ContactAccessor {
|
||||
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
|
||||
private static final String TAG = "ContactAccessor";
|
||||
|
||||
private static final int CONTACT_CURSOR_NAME = 0;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.util.AsyncLoader;
|
||||
|
||||
public class PagingMediaLoader extends AsyncLoader<DcMediaGalleryElement> {
|
||||
|
||||
private static final String TAG = PagingMediaLoader.class.getSimpleName();
|
||||
private static final String TAG = "PagingMediaLoader";
|
||||
|
||||
private final DcMsg msg;
|
||||
private final boolean leftIsRecent;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class ActiveLocationChats {
|
||||
|
||||
private static final String PREFS_NAME = "location_streaming";
|
||||
private static final String KEY_ACTIVE = "active_chat_ids";
|
||||
|
||||
private ActiveLocationChats() {}
|
||||
|
||||
private static SharedPreferences prefs(Context context) {
|
||||
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a chat. Uses commit() to guarantee the write reaches disk before the process can die to
|
||||
* preserve the superset invariant.
|
||||
*/
|
||||
static void add(Context context, int chatId) {
|
||||
Set<String> current = new HashSet<>(getAll(context));
|
||||
current.add(String.valueOf(chatId));
|
||||
prefs(context).edit().putStringSet(KEY_ACTIVE, current).commit();
|
||||
}
|
||||
|
||||
public static void remove(Context context, int chatId) {
|
||||
Set<String> current = new HashSet<>(getAll(context));
|
||||
current.remove(String.valueOf(chatId));
|
||||
prefs(context).edit().putStringSet(KEY_ACTIVE, current).apply();
|
||||
}
|
||||
|
||||
static void clear(Context context) {
|
||||
prefs(context).edit().remove(KEY_ACTIVE).apply();
|
||||
}
|
||||
|
||||
static Set<Integer> getAllIds(Context context) {
|
||||
Set<Integer> ids = new HashSet<>();
|
||||
for (String s : getAll(context)) {
|
||||
try {
|
||||
ids.add(Integer.parseInt(s));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static Set<String> getAll(Context context) {
|
||||
return prefs(context).getStringSet(KEY_ACTIVE, new HashSet<>());
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
import java.util.Observable;
|
||||
|
||||
public class DcLocation extends Observable {
|
||||
private static final String TAG = DcLocation.class.getSimpleName();
|
||||
private Location lastLocation;
|
||||
private static DcLocation instance;
|
||||
private static final int TIMEOUT = 1000 * 15;
|
||||
private static final int EARTH_RADIUS = 6371;
|
||||
|
||||
private DcLocation() {
|
||||
lastLocation = getDefault();
|
||||
}
|
||||
|
||||
public static DcLocation getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new DcLocation();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Location getLastLocation() {
|
||||
return lastLocation;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return !"?".equals(lastLocation.getProvider());
|
||||
}
|
||||
|
||||
void updateLocation(Location location) {
|
||||
if (isBetterLocation(location, lastLocation)) {
|
||||
lastLocation = location;
|
||||
|
||||
instance.setChanged();
|
||||
instance.notifyObservers();
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
updateLocation(getDefault());
|
||||
}
|
||||
|
||||
private Location getDefault() {
|
||||
return new Location("?");
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.android.com/guide/topics/location/strategies Determines whether one Location
|
||||
* reading is better than the current Location fix
|
||||
*
|
||||
* @param location The new Location that you want to evaluate
|
||||
* @param currentBestLocation The current Location fix, to which you want to compare the new one
|
||||
*/
|
||||
private boolean isBetterLocation(Location location, Location currentBestLocation) {
|
||||
if (currentBestLocation == null) {
|
||||
// A new location is always better than no location
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check whether the new location fix is newer or older
|
||||
long timeDelta = location.getTime() - currentBestLocation.getTime();
|
||||
boolean isSignificantlyOlder = timeDelta < -TIMEOUT;
|
||||
|
||||
if (isSignificantlyOlder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether the new location fix is more or less accurate
|
||||
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
|
||||
Log.d(TAG, "accuracyDelta: " + accuracyDelta);
|
||||
boolean isSignificantlyMoreAccurate = accuracyDelta > 50;
|
||||
boolean isSameProvider =
|
||||
isSameProvider(location.getProvider(), currentBestLocation.getProvider());
|
||||
|
||||
if (isSignificantlyMoreAccurate && isSameProvider) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isMoreAccurate = accuracyDelta > 0;
|
||||
double distance = distance(location, currentBestLocation);
|
||||
return hasLocationChanged(distance) && isMoreAccurate
|
||||
|| hasLocationSignificantlyChanged(distance);
|
||||
}
|
||||
|
||||
private boolean hasLocationSignificantlyChanged(double distance) {
|
||||
return distance > 30D;
|
||||
}
|
||||
|
||||
private boolean hasLocationChanged(double distance) {
|
||||
return distance > 10D;
|
||||
}
|
||||
|
||||
private double distance(Location location, Location currentBestLocation) {
|
||||
|
||||
double startLat = location.getLatitude();
|
||||
double startLong = location.getLongitude();
|
||||
double endLat = currentBestLocation.getLatitude();
|
||||
double endLong = currentBestLocation.getLongitude();
|
||||
|
||||
double dLat = Math.toRadians(endLat - startLat);
|
||||
double dLong = Math.toRadians(endLong - startLong);
|
||||
|
||||
startLat = Math.toRadians(startLat);
|
||||
endLat = Math.toRadians(endLat);
|
||||
|
||||
double a = haversin(dLat) + Math.cos(startLat) * Math.cos(endLat) * haversin(dLong);
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
double distance = EARTH_RADIUS * c * 1000;
|
||||
Log.d(TAG, "Distance between location updates: " + distance);
|
||||
return distance;
|
||||
}
|
||||
|
||||
private double haversin(double val) {
|
||||
return Math.pow(Math.sin(val / 2), 2);
|
||||
}
|
||||
|
||||
/** Checks whether two providers are the same */
|
||||
private boolean isSameProvider(String provider1, String provider2) {
|
||||
if (provider1 == null) {
|
||||
return provider2 == null;
|
||||
}
|
||||
return provider1.equals(provider2);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import static android.content.Context.BIND_AUTO_CREATE;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.location.Location;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import com.b44t.messenger.DcContext;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
|
||||
public class DcLocationManager implements Observer {
|
||||
|
||||
private static final String TAG = DcLocationManager.class.getSimpleName();
|
||||
private LocationBackgroundService.LocationBackgroundServiceBinder serviceBinder;
|
||||
private final Context context;
|
||||
private DcLocation dcLocation = DcLocation.getInstance();
|
||||
private final LinkedList<Integer> pendingShareLastLocation = new LinkedList<>();
|
||||
private final ServiceConnection serviceConnection =
|
||||
new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
Log.d(TAG, "background service connected");
|
||||
serviceBinder = (LocationBackgroundService.LocationBackgroundServiceBinder) service;
|
||||
while (!pendingShareLastLocation.isEmpty()) {
|
||||
shareLastLocation(pendingShareLastLocation.pop());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.d(TAG, "background service disconnected");
|
||||
serviceBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
public DcLocationManager(Context context, DcContext dcContext) {
|
||||
this.context = context.getApplicationContext();
|
||||
DcLocation.getInstance().addObserver(this);
|
||||
if (dcContext.isSendingLocationsToChat(0)) {
|
||||
startLocationEngine();
|
||||
}
|
||||
}
|
||||
|
||||
public void startLocationEngine() {
|
||||
if (serviceBinder == null) {
|
||||
Intent intent = new Intent(context.getApplicationContext(), LocationBackgroundService.class);
|
||||
context.bindService(intent, serviceConnection, BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopLocationEngine() {
|
||||
if (serviceBinder == null) {
|
||||
return;
|
||||
}
|
||||
context.unbindService(serviceConnection);
|
||||
serviceBinder.stop();
|
||||
serviceBinder = null;
|
||||
}
|
||||
|
||||
public void stopSharingLocation(int chatId) {
|
||||
DcHelper.getContext(context).sendLocationsToChat(chatId, 0);
|
||||
if (!DcHelper.getContext(context).isSendingLocationsToChat(0)) {
|
||||
stopLocationEngine();
|
||||
}
|
||||
}
|
||||
|
||||
public void shareLocation(int duration, int chatId) {
|
||||
startLocationEngine();
|
||||
Log.d(TAG, String.format("Share location in chat %d for %d seconds", chatId, duration));
|
||||
DcHelper.getContext(context).sendLocationsToChat(chatId, duration);
|
||||
if (dcLocation.isValid()) {
|
||||
writeDcLocationUpdateMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public void shareLastLocation(int chatId) {
|
||||
if (serviceBinder == null) {
|
||||
pendingShareLastLocation.push(chatId);
|
||||
startLocationEngine();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dcLocation.isValid()) {
|
||||
DcHelper.getContext(context).sendLocationsToChat(chatId, 1);
|
||||
writeDcLocationUpdateMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
if (o instanceof DcLocation) {
|
||||
dcLocation = (DcLocation) o;
|
||||
if (dcLocation.isValid()) {
|
||||
writeDcLocationUpdateMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDcLocationUpdateMessage() {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Share location: "
|
||||
+ dcLocation.getLastLocation().getLatitude()
|
||||
+ ", "
|
||||
+ dcLocation.getLastLocation().getLongitude());
|
||||
Location lastLocation = dcLocation.getLastLocation();
|
||||
|
||||
boolean continueLocationStreaming =
|
||||
DcHelper.getContext(context)
|
||||
.setLocation(
|
||||
(float) lastLocation.getLatitude(),
|
||||
(float) lastLocation.getLongitude(),
|
||||
lastLocation.getAccuracy());
|
||||
if (!continueLocationStreaming) {
|
||||
stopLocationEngine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class LocationBackgroundService extends Service {
|
||||
|
||||
private static final int INITIAL_TIMEOUT = 1000 * 60 * 2;
|
||||
private static final String TAG = LocationBackgroundService.class.getSimpleName();
|
||||
private LocationManager locationManager = null;
|
||||
private static final int LOCATION_INTERVAL = 1000;
|
||||
private static final float LOCATION_DISTANCE = 25F;
|
||||
ServiceLocationListener locationListener;
|
||||
|
||||
private final IBinder mBinder = new LocationBackgroundServiceBinder();
|
||||
|
||||
@Override
|
||||
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
|
||||
return super.bindService(service, conn, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
locationManager =
|
||||
(LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "Unable to initialize location service");
|
||||
return;
|
||||
}
|
||||
|
||||
locationListener = new ServiceLocationListener();
|
||||
Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
||||
if (lastLocation != null) {
|
||||
long locationAge = System.currentTimeMillis() - lastLocation.getTime();
|
||||
if (locationAge <= 600 * 1000) { // not older than 10 minutes
|
||||
DcLocation.getInstance().updateLocation(lastLocation);
|
||||
}
|
||||
}
|
||||
// requestLocationUpdate(LocationManager.NETWORK_PROVIDER);
|
||||
requestLocationUpdate(LocationManager.GPS_PROVIDER);
|
||||
initialLocationUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
super.onStartCommand(intent, flags, startId);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (locationManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
locationManager.removeUpdates(locationListener);
|
||||
} catch (Exception ex) {
|
||||
Log.i(TAG, "fail to remove location listeners, ignore", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void requestLocationUpdate(String provider) {
|
||||
try {
|
||||
locationManager.requestLocationUpdates(
|
||||
provider, LOCATION_INTERVAL, LOCATION_DISTANCE, locationListener);
|
||||
} catch (SecurityException | IllegalArgumentException ex) {
|
||||
Log.e(
|
||||
TAG,
|
||||
String.format("Unable to request %s provider based location updates.", provider),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void initialLocationUpdate() {
|
||||
try {
|
||||
Location gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
||||
if (gpsLocation != null
|
||||
&& System.currentTimeMillis() - gpsLocation.getTime() < INITIAL_TIMEOUT) {
|
||||
locationListener.onLocationChanged(gpsLocation);
|
||||
}
|
||||
|
||||
} catch (NullPointerException | SecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
class LocationBackgroundServiceBinder extends Binder {
|
||||
LocationBackgroundServiceBinder getService() {
|
||||
return LocationBackgroundServiceBinder.this;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
DcLocation.getInstance().reset();
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private class ServiceLocationListener implements LocationListener {
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(@NonNull Location location) {
|
||||
Log.d(TAG, "onLocationChanged: " + location);
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
DcLocation.getInstance().updateLocation(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(@NonNull String provider) {
|
||||
Log.e(TAG, "onProviderDisabled: " + provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(@NonNull String provider) {
|
||||
Log.e(TAG, "onProviderEnabled: " + provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
Log.e(TAG, "onStatusChanged: " + provider + " status: " + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.location.Location;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
/**
|
||||
* Process-wide holder for the current streamed location. Foreground service writes, UI observes.
|
||||
*/
|
||||
public final class LocationData {
|
||||
|
||||
private static final LocationData INSTANCE = new LocationData();
|
||||
|
||||
private final MutableLiveData<Location> liveLocation = new MutableLiveData<>();
|
||||
|
||||
private LocationData() {}
|
||||
|
||||
public static LocationData getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public LiveData<Location> observable() {
|
||||
return liveLocation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Location current() {
|
||||
return liveLocation.getValue();
|
||||
}
|
||||
|
||||
void post(@NonNull Location location) {
|
||||
liveLocation.postValue(location);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
liveLocation.postValue(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/** Abstraction over platform LocationManager and GMS FusedLocationProviderClient. */
|
||||
public interface LocationSource {
|
||||
|
||||
void startUpdates(@NonNull Context context, @NonNull Callback callback);
|
||||
|
||||
void stopUpdates();
|
||||
|
||||
interface Callback {
|
||||
void onLocationUpdate(@NonNull Location location);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
|
||||
public class LocationStreamingService extends Service {
|
||||
|
||||
private static final String TAG = "LocationStreamingService";
|
||||
private static final String ACTION_STOP = "org.thoughtcrime.securesms.geolocation.STOP_STREAMING";
|
||||
private static final int NOTIFICATION_ID = 8801;
|
||||
private static final String CHANNEL_ID = "location_streaming";
|
||||
|
||||
private static volatile boolean running = false;
|
||||
|
||||
private LocationSource source;
|
||||
private Location lastPublished;
|
||||
|
||||
// static API
|
||||
|
||||
/** Register a chat for location updates, then ensure the service is running. */
|
||||
public static void startSharing(Context context, int chatId, int durationSeconds) {
|
||||
ActiveLocationChats.add(context, chatId);
|
||||
DcHelper.getContext(context).sendLocationsToChat(chatId, durationSeconds);
|
||||
ContextCompat.startForegroundService(
|
||||
context, new Intent(context, LocationStreamingService.class));
|
||||
}
|
||||
|
||||
/** Unregister a chat. If no chats remain, stop the service. */
|
||||
public static void stopSharing(Context context, int chatId) {
|
||||
ActiveLocationChats.remove(context, chatId);
|
||||
DcHelper.getContext(context).sendLocationsToChat(chatId, 0);
|
||||
if (!DcHelper.getContext(context).isSendingLocationsToChat(0)) {
|
||||
context.stopService(new Intent(context, LocationStreamingService.class));
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureRunning(Context context) {
|
||||
if (!hasLocationPermission(context)) {
|
||||
for (int chatId : ActiveLocationChats.getAllIds(context)) {
|
||||
DcHelper.getContext(context).sendLocationsToChat(chatId, 0);
|
||||
}
|
||||
ActiveLocationChats.clear(context);
|
||||
return;
|
||||
}
|
||||
ContextCompat.startForegroundService(
|
||||
context, new Intent(context, LocationStreamingService.class));
|
||||
}
|
||||
|
||||
public static boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
// lifecycle
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
if (!hasLocationPermission(this)) {
|
||||
Log.w(TAG, "Location permission not granted, stopping");
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
promoteToForeground();
|
||||
beginLocationUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
|
||||
if (intent != null && ACTION_STOP.equals(intent.getAction())) {
|
||||
stopAllSharing();
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
// If the service is already running, and we already have a fix,
|
||||
// push it immediately.
|
||||
if (lastPublished != null) {
|
||||
publishAndWrite(lastPublished);
|
||||
}
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void stopAllSharing() {
|
||||
for (int chatId : ActiveLocationChats.getAllIds(this)) {
|
||||
DcHelper.getContext(this).sendLocationsToChat(chatId, 0);
|
||||
}
|
||||
ActiveLocationChats.clear(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
running = false;
|
||||
if (source != null) {
|
||||
source.stopUpdates();
|
||||
source = null;
|
||||
}
|
||||
LocationData.getInstance().clear();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(int startId) {
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(int startId, int fgsType) {
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
// location
|
||||
|
||||
private void beginLocationUpdates() {
|
||||
source = LocationSourceFactory.create(this);
|
||||
source.startUpdates(this, this::onNewLocation);
|
||||
}
|
||||
|
||||
private void onNewLocation(Location location) {
|
||||
Log.d(TAG, "onNewLocation raw: " + location);
|
||||
publishAndWrite(location);
|
||||
lastPublished = location;
|
||||
}
|
||||
|
||||
private void publishAndWrite(Location location) {
|
||||
LocationData.getInstance().post(location);
|
||||
|
||||
boolean keepGoing =
|
||||
DcHelper.getContext(this)
|
||||
.setLocation(
|
||||
(float) location.getLatitude(),
|
||||
(float) location.getLongitude(),
|
||||
location.getAccuracy());
|
||||
Log.d(TAG, "keepGoing: " + keepGoing);
|
||||
|
||||
if (!keepGoing) {
|
||||
stopAllSharing();
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
// foreground / notification
|
||||
|
||||
private void promoteToForeground() {
|
||||
ensureNotificationChannel();
|
||||
Notification notification = buildNotification();
|
||||
try {
|
||||
int type =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
? ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
|
||||
: 0;
|
||||
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, type);
|
||||
} catch (Exception e) {
|
||||
// SecurityException on API 34+ if permission missing,
|
||||
// ForegroundServiceStartNotAllowedException on API 31+ if in background.
|
||||
Log.e(TAG, "Cannot promote to foreground", e);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel =
|
||||
new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
getString(R.string.location_streaming_notification_title),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
channel.setDescription(getString(R.string.location_streaming_channel_desc));
|
||||
channel.setShowBadge(false);
|
||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||
if (nm != null) nm.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private Notification buildNotification() {
|
||||
Intent tapIntent = new Intent(this, ConversationListActivity.class);
|
||||
PendingIntent contentPendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
this, 0, tapIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
Intent stopIntent = new Intent(this, LocationStreamingService.class);
|
||||
stopIntent.setAction(ACTION_STOP);
|
||||
PendingIntent stopPendingIntent =
|
||||
PendingIntent.getService(this, 1, stopIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.location_streaming_notification_title))
|
||||
.setContentText(getString(R.string.location_streaming_notification_text))
|
||||
.setSmallIcon(R.drawable.ic_location_on_white_24dp)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.addAction(
|
||||
R.drawable.ic_stop_circle, getString(R.string.stop_sharing_location), stopPendingIntent)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean hasLocationPermission(Context context) {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
context, android.Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED
|
||||
|| ContextCompat.checkSelfPermission(
|
||||
context, android.Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.thoughtcrime.securesms.geolocation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.location.LocationListenerCompat;
|
||||
import androidx.core.location.LocationManagerCompat;
|
||||
import androidx.core.location.LocationRequestCompat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class PlatformLocationSource implements LocationSource {
|
||||
|
||||
private static final String TAG = "PlatformLocationSource";
|
||||
private static final long UPDATE_INTERVAL_MS = 0;
|
||||
|
||||
private LocationManager locationManager;
|
||||
private final List<LocationListenerCompat> activeListeners = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void startUpdates(@NonNull Context context, @NonNull Callback callback) {
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "LocationManager unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean registered = false;
|
||||
|
||||
// API 31+: try the platform fused provider first
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
registered = requestProvider(context, LocationManager.FUSED_PROVIDER, callback);
|
||||
}
|
||||
|
||||
// Fall back (or complement) with individual providers
|
||||
if (!registered) {
|
||||
requestProvider(context, LocationManager.GPS_PROVIDER, callback);
|
||||
requestProvider(context, LocationManager.NETWORK_PROVIDER, callback);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requestProvider(Context context, String provider, Callback callback) {
|
||||
if (locationManager == null) return false;
|
||||
|
||||
if (!locationManager.isProviderEnabled(provider)) {
|
||||
Log.d(TAG, "Provider " + provider + " not enabled, skipping");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
LocationRequestCompat request =
|
||||
new LocationRequestCompat.Builder(UPDATE_INTERVAL_MS)
|
||||
.setMinUpdateDistanceMeters(0)
|
||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||
.build();
|
||||
|
||||
LocationListenerCompat listener = callback::onLocationUpdate;
|
||||
Executor mainExecutor = ContextCompat.getMainExecutor(context);
|
||||
|
||||
LocationManagerCompat.requestLocationUpdates(
|
||||
locationManager, provider, request, mainExecutor, listener);
|
||||
activeListeners.add(listener);
|
||||
Log.d(TAG, "Registered on provider: " + provider);
|
||||
return true;
|
||||
} catch (SecurityException | IllegalArgumentException e) {
|
||||
Log.e(TAG, "Cannot request " + provider + " updates", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopUpdates() {
|
||||
if (locationManager != null) {
|
||||
for (LocationListenerCompat listener : activeListeners) {
|
||||
try {
|
||||
locationManager.removeUpdates(listener);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error removing listener", e);
|
||||
}
|
||||
}
|
||||
activeListeners.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
class JobConsumer extends Thread {
|
||||
|
||||
private static final String TAG = JobConsumer.class.getSimpleName();
|
||||
private static final String TAG = "JobConsumer";
|
||||
|
||||
enum JobResult {
|
||||
SUCCESS,
|
||||
|
||||
@@ -47,7 +47,6 @@ import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.ShareLocationDialog;
|
||||
@@ -64,7 +63,8 @@ import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel;
|
||||
import org.thoughtcrime.securesms.components.audioplay.AudioView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
|
||||
import org.thoughtcrime.securesms.geolocation.ActiveLocationChats;
|
||||
import org.thoughtcrime.securesms.geolocation.LocationStreamingService;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
|
||||
@@ -75,7 +75,7 @@ import org.thoughtcrime.securesms.util.views.Stub;
|
||||
|
||||
public class AttachmentManager {
|
||||
|
||||
private static final String TAG = AttachmentManager.class.getSimpleName();
|
||||
private static final String TAG = "AttachmentManager";
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull Stub<View> attachmentViewStub;
|
||||
@@ -490,47 +490,36 @@ public class AttachmentManager {
|
||||
}
|
||||
|
||||
public static void selectLocation(Activity activity, int chatId) {
|
||||
ApplicationContext applicationContext = ApplicationContext.getInstance(activity);
|
||||
DcLocationManager dcLocationManager = applicationContext.getLocationManager();
|
||||
Context appContext = activity.getApplicationContext();
|
||||
|
||||
if (DcHelper.getContext(applicationContext).isSendingLocationsToChat(chatId)) {
|
||||
dcLocationManager.stopSharingLocation(chatId);
|
||||
return;
|
||||
if (DcHelper.getContext(appContext).isSendingLocationsToChat(chatId)) {
|
||||
if (LocationStreamingService.isRunning()) {
|
||||
LocationStreamingService.stopSharing(appContext, chatId);
|
||||
return;
|
||||
}
|
||||
// Stale — service is dead but chat layer still thinks it's sharing.
|
||||
// Clean up this chat and fall through to the fresh start flow.
|
||||
ActiveLocationChats.remove(appContext, chatId);
|
||||
DcHelper.getContext(appContext).sendLocationsToChat(chatId, 0);
|
||||
}
|
||||
|
||||
// see
|
||||
// https://support.google.com/googleplay/android-developer/answer/9799150#zippy=%2Cstep-provide-prominent-in-app-disclosure
|
||||
// for rationale dialog requirements
|
||||
Permissions.PermissionsBuilder permissionsBuilder =
|
||||
Permissions.with(activity)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(
|
||||
"To share your live location with chat members, allow ArcaneChat to use your location data.\n\nTo make live location work gaplessly, location data is used even when the app is closed or not in use.",
|
||||
R.drawable.ic_location_on_white_24dp)
|
||||
.withPermanentDenialDialog(
|
||||
activity.getString(R.string.perm_explain_access_to_location_denied))
|
||||
.onAllGranted(
|
||||
() -> {
|
||||
ShareLocationDialog.show(
|
||||
activity,
|
||||
durationInSeconds -> {
|
||||
if (durationInSeconds == 1) {
|
||||
dcLocationManager.shareLastLocation(chatId);
|
||||
} else {
|
||||
dcLocationManager.shareLocation(durationInSeconds, chatId);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
||||
permissionsBuilder.request(
|
||||
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||
} else {
|
||||
permissionsBuilder.request(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||
}
|
||||
permissionsBuilder.execute();
|
||||
Permissions.with(activity)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(
|
||||
activity.getString(R.string.location_rationale), R.drawable.ic_location_on_white_24dp)
|
||||
.withPermanentDenialDialog(
|
||||
activity.getString(R.string.perm_explain_access_to_location_denied))
|
||||
.onAllGranted(
|
||||
() -> {
|
||||
ShareLocationDialog.show(
|
||||
activity,
|
||||
durationInSeconds ->
|
||||
LocationStreamingService.startSharing(appContext, chatId, durationInSeconds));
|
||||
})
|
||||
.request(
|
||||
android.Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
android.Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private @Nullable Uri getSlideUri() {
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.io.InputStream;
|
||||
|
||||
class DecryptableStreamLocalUriFetcher extends StreamLocalUriFetcher {
|
||||
|
||||
private static final String TAG = DecryptableStreamLocalUriFetcher.class.getSimpleName();
|
||||
private static final String TAG = "DecryptableStreamLocalUriFetcher";
|
||||
|
||||
private final Context context;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import android.util.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class InChatSounds {
|
||||
private static final String TAG = InChatSounds.class.getSimpleName();
|
||||
private static final String TAG = "InChatSounds";
|
||||
private static volatile InChatSounds instance;
|
||||
|
||||
private SoundPool soundPool = null;
|
||||
|
||||
@@ -54,7 +54,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class NotificationCenter {
|
||||
private static final String TAG = NotificationCenter.class.getSimpleName();
|
||||
private static final String TAG = "NotificationCenter";
|
||||
@NonNull private final ApplicationContext context;
|
||||
private volatile ChatData visibleChat = null;
|
||||
private volatile Pair<Integer, Integer> visibleWebxdc = null;
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
/** Get the response text from the Wearable Device and sends an message as a reply */
|
||||
public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String TAG = RemoteReplyReceiver.class.getSimpleName();
|
||||
public static final String TAG = "RemoteReplyReceiver";
|
||||
public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY";
|
||||
public static final String ACCOUNT_ID_EXTRA = "account_id";
|
||||
public static final String CHAT_ID_EXTRA = "chat_id";
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = AdvancedPreferenceFragment.class.getSimpleName();
|
||||
private static final String TAG = "AdvancedPreferenceFragment";
|
||||
|
||||
private ListPreference showEmails;
|
||||
CheckBoxPreference selfReportingCheckbox;
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||
public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment
|
||||
implements Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final String TAG = NotificationsPreferenceFragment.class.getSimpleName();
|
||||
private static final String TAG = "NotificationsPreferenceFragment";
|
||||
|
||||
private CheckBoxPreference ignoreBattery;
|
||||
private CheckBoxPreference notificationsEnabled;
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class PersistentBlobProvider {
|
||||
|
||||
private static final String TAG = PersistentBlobProvider.class.getSimpleName();
|
||||
private static final String TAG = "PersistentBlobProvider";
|
||||
|
||||
private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture-new";
|
||||
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class BackupProviderFragment extends Fragment implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = BackupProviderFragment.class.getSimpleName();
|
||||
private static final String TAG = "BackupProviderFragment";
|
||||
|
||||
private DcContext dcContext;
|
||||
private DcBackupProvider dcBackupProvider;
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class BackupReceiverFragment extends Fragment implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = BackupProviderFragment.class.getSimpleName();
|
||||
private static final String TAG = "BackupProviderFragment";
|
||||
|
||||
private DcContext dcContext;
|
||||
private TextView statusLine;
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class BackupTransferActivity extends BaseActionBarActivity {
|
||||
|
||||
private static final String TAG = BackupTransferActivity.class.getSimpleName();
|
||||
private static final String TAG = "BackupTransferActivity";
|
||||
|
||||
public enum TransferMode {
|
||||
INVALID(0),
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class QrActivity extends BaseActionBarActivity implements View.OnClickListener {
|
||||
|
||||
private static final String TAG = QrActivity.class.getSimpleName();
|
||||
private static final String TAG = "QrActivity";
|
||||
public static final String EXTRA_SCAN_RELAY = "scan_relay";
|
||||
|
||||
private static final int REQUEST_CODE_IMAGE = 46243;
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
||||
|
||||
public class QrCodeHandler {
|
||||
private static final String TAG = QrCodeHandler.class.getSimpleName();
|
||||
private static final String TAG = "QrCodeHandler";
|
||||
|
||||
public static int SECUREJOIN_SOURCE_EXTERNAL_LINK = 1;
|
||||
public static int SECUREJOIN_SOURCE_INTERNAL_LINK = 2;
|
||||
@@ -363,20 +363,24 @@ public class QrCodeHandler {
|
||||
SecurejoinSource source,
|
||||
SecurejoinUiPath uipath) {
|
||||
String msg;
|
||||
int positiveButton;
|
||||
switch (qrParsed.getState()) {
|
||||
case DcContext.DC_QR_ASK_VERIFYGROUP:
|
||||
msg = activity.getString(R.string.qrscan_ask_join_group, qrParsed.getText1());
|
||||
positiveButton = R.string.join_group;
|
||||
break;
|
||||
case DcContext.DC_QR_ASK_JOIN_BROADCAST:
|
||||
msg = activity.getString(R.string.qrscan_ask_join_channel, qrParsed.getText1());
|
||||
positiveButton = R.string.join_channel;
|
||||
break;
|
||||
default:
|
||||
msg = activity.getString(R.string.ask_start_chat_with, name);
|
||||
positiveButton = R.string.ok;
|
||||
break;
|
||||
}
|
||||
builder.setMessage(msg);
|
||||
builder.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
positiveButton,
|
||||
(dialogInterface, i) -> {
|
||||
secureJoinByQr(qrRawString, source, uipath);
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class QrScanFragment extends Fragment {
|
||||
|
||||
private static final String TAG = QrScanFragment.class.getSimpleName();
|
||||
private static final String TAG = "QrScanFragment";
|
||||
|
||||
private CompoundBarcodeView barcodeScannerView;
|
||||
private MyCaptureManager capture;
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class QrShowFragment extends Fragment implements DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = QrShowFragment.class.getSimpleName();
|
||||
private static final String TAG = "QrShowFragment";
|
||||
public static final int WHITE = 0xFFFFFFFF;
|
||||
private static final int BLACK = 0xFF000000;
|
||||
private static final int WIDTH = 400;
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class ReactionsDetailsFragment extends DialogFragment
|
||||
implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = ReactionsDetailsFragment.class.getSimpleName();
|
||||
private static final String TAG = "ReactionsDetailsFragment";
|
||||
private static final String ARG_MSG_ID = "msg_id";
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class EditRelayActivity extends BaseActionBarActivity
|
||||
PORT,
|
||||
}
|
||||
|
||||
private static final String TAG = EditRelayActivity.class.getSimpleName();
|
||||
private static final String TAG = "EditRelayActivity";
|
||||
public static final String EXTRA_ADDR = "extra_addr";
|
||||
|
||||
private TextInputEditText emailInput;
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
public class RelayListActivity extends BaseActionBarActivity
|
||||
implements RelayListAdapter.OnRelayClickListener, DcEventCenter.DcEventDelegate {
|
||||
|
||||
private static final String TAG = RelayListActivity.class.getSimpleName();
|
||||
private static final String TAG = "RelayListActivity";
|
||||
public static final String EXTRA_QR_DATA = "qr_data";
|
||||
|
||||
private RelayListAdapter adapter;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user