Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/bundler/docs/nokogiri-1.16.3
Browse files Browse the repository at this point in the history
  • Loading branch information
ostrya authored Aug 28, 2024
2 parents 01ca3be + fc93ac4 commit 30ba61a
Show file tree
Hide file tree
Showing 47 changed files with 677 additions and 314 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ For details on which data this app processes and how it does so, please have a l
The app uses the default Android CA trust store for checking the server certificate validity. You can simply add your
certificate via:

* Android 4 - 7:
* Android 5 - 7:
* `Security``Install from SD card`
* Android 8 - 9:
* `Security & location``Encryption & credentials``Install from SD card`
Expand Down Expand Up @@ -89,7 +89,7 @@ Make sure your PKCS#12 keystore file has the `.pfx` extension, otherwise Android
To be able to select this client certificate in Presence Publisher, you first need to add it to
the Android Keystore. This works similar to the process for the server certificate:

* Android 4 - 7:
* Android 5 - 7:
* `Security``Install from SD card`
* Android 8 - 9:
* `Security & location``Encryption & credentials``Install from SD card`
Expand All @@ -112,10 +112,9 @@ After you have imported your client certificate, you will be able to choose it f
* BLUETOOTH_ADMIN: necessary up to Android 11 to discover beacons
* BLUETOOTH_CONNECT: on Android 12+, necessary to read beacon names
* BLUETOOTH_SCAN: on Android 12+, necessary to discover beacons
* FOREGROUND_SERVICE: necessary on Android 9+ to run the app reliably
* FOREGROUND_SERVICE: on Android 9+, necessary to run the app reliably
* FOREGROUND_SERVICE_DATA_SYNC: on Android 14+, necessary to send MQTT messages
* FOREGROUND_SERVICE_LOCATION: on Android 14+, necessary to retrieve Wi-Fi name
* INTERNET: only necessary if your MQTT server is not running locally
* POST_NOTIFICATIONS: necessary in Android 13+ to create notifications
* POST_NOTIFICATIONS: on Android 13+, necessary to create notifications
* RECEIVE_BOOT_COMPLETED: necessary to start service on start-up
* REQUEST_IGNORE_BATTERY_OPTIMIZATIONS: on Android 6+, necessary to request disabling battery optimization
37 changes: 20 additions & 17 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ buildscript {
google()
}
dependencies {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
classpath("org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r")
}
}

plugins {
id("com.github.triplet.play") version "3.8.4"
id("com.jaredsburrows.license") version "0.9.3"
id("com.github.triplet.play") version "3.10.1"
id("com.jaredsburrows.license") version "0.9.8"
id("com.android.application")
}

Expand All @@ -40,15 +40,15 @@ fun isTagged(): Boolean {
}

android {
compileSdk = 34
compileSdk = 35
defaultConfig {
applicationId = "org.ostrya.presencepublisher"
minSdk = 19
minSdk = 21
multiDexEnabled = true
targetSdk = 34
targetSdk = 35
vectorDrawables.useSupportLibrary = true
versionCode = 51
versionName = "2.5.3"
versionCode = 53
versionName = "2.6.0"
javaCompileOptions {
annotationProcessorOptions {
argument("room.schemaLocation", "$projectDir/schemas")
Expand Down Expand Up @@ -88,6 +88,9 @@ android {
abortOnError = false
}
namespace = "org.ostrya.presencepublisher"
buildFeatures {
buildConfig = true
}
}

val checkParameters by tasks.registering {
Expand Down Expand Up @@ -135,28 +138,28 @@ play {
}

dependencies {
val roomVersion = "2.6.0-beta01"
val roomVersion = "2.6.1"

implementation("androidx.appcompat:appcompat:1.7.0-alpha03")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.preference:preference:1.2.1")
implementation("androidx.room:room-guava:$roomVersion")
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.security:security-crypto:1.1.0-alpha06")
implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
implementation("androidx.work:work-runtime:2.8.1")
implementation("com.google.android.material:material:1.9.0")
implementation("com.google.guava:guava:32.1.2-android")
implementation("org.altbeacon:android-beacon-library:2.19.6")
implementation("androidx.viewpager2:viewpager2:1.1.0")
implementation("androidx.work:work-runtime:2.9.1")
implementation("com.google.android.material:material:1.12.0")
implementation("com.google.guava:guava:33.3.0-android")
implementation("org.altbeacon:android-beacon-library:2.20.6")
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
testImplementation("junit:junit:4.13.2")
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("org.assertj:assertj-core:3.26.3")
testImplementation("org.mockito:mockito-inline:5.2.0")

annotationProcessor("androidx.room:room-compiler:$roomVersion")

coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}

tasks.register("printVersion") {
Expand Down
12 changes: 10 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- Request specific foreground permissions on Android 14+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Expand Down Expand Up @@ -60,9 +59,18 @@
</intent-filter>
</receiver>

<receiver android:name=".receiver.NetworkReceiver" android:exported="true">
<intent-filter>
<!-- only works until API level 23, see https://developer.android.com/topic/performance/background-optimization -->
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE"
tools:ignore="BatteryLife" />
</intent-filter>
</receiver>

<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync|location"
android:foregroundServiceType="dataSync"
tools:node="merge" />
</application>

Expand Down
11 changes: 4 additions & 7 deletions app/src/main/java/org/ostrya/presencepublisher/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,12 @@ public void onCreate(Bundle savedInstanceState) {
@Override
protected void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);
handler.initialize();
}

@Override
protected void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceListener);
}

@Override
protected void onDestroy() {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceListener);
super.onDestroy();
handler = null;
}
Expand All @@ -86,11 +80,14 @@ private void onSharedPreferenceChanged(SharedPreferences preferences, String key
}

private void handleConsentChange() {
PresencePublisher application = (PresencePublisher) getApplication();
if (sharedPreferences.getBoolean(LOCATION_CONSENT, false)) {
DatabaseLogger.i(TAG, "User consented to location access, initializing.");
application.setUpConditionCallbacks(sharedPreferences);
handler.initialize();
} else {
DatabaseLogger.i(TAG, "User revoked location access consent, stopping schedule.");
application.removeConditionCallbacks();
new Scheduler(this).stopSchedule();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.ostrya.presencepublisher;

import static org.ostrya.presencepublisher.preference.about.LocationConsentPreference.LOCATION_CONSENT;
import static org.ostrya.presencepublisher.preference.condition.BeaconCategorySupport.BEACON_LIST;

import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkRequest;
import android.os.Build;
import android.util.Log;

import androidx.multidex.MultiDexApplication;
Expand All @@ -22,33 +26,48 @@
import org.ostrya.presencepublisher.log.LogUncaughtExceptionHandler;
import org.ostrya.presencepublisher.log.PahoNoopLogger;
import org.ostrya.presencepublisher.mqtt.context.condition.beacon.PresenceBeaconManager;
import org.ostrya.presencepublisher.mqtt.context.condition.network.NetworkService;
import org.ostrya.presencepublisher.notification.NotificationFactory;
import org.ostrya.presencepublisher.preference.about.NightModePreference;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;

public class PresencePublisher extends MultiDexApplication {
private static final String TAG = "PresencePublisher";
public static final int STATUS_NOTIFICATION_ID = 1;
public static final int NOTIFICATION_REQUEST_CODE = 2;
public static final int PROGRESS_NOTIFICATION_ID = 3;

public static final Object LOCK = new Object();

public static final String MQTT_CLIENT_ID = "mqttClientId";

private static final String USE_WORKER_1 = "useWorker1";

private final AtomicReference<ConnectivityManager.NetworkCallback> currentCallback =
new AtomicReference<>();

@Override
public void onCreate() {
super.onCreate();
initLogger();
initBeaconManager();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
if (preferences.getBoolean(LOCATION_CONSENT, false)) {
setUpConditionCallbacks(preferences);
}
new NotificationFactory(this).createNotificationChannels();
initClientId();
initClientId(preferences);
NightModePreference.updateCurrentNightMode(this);
DynamicColors.applyToActivitiesIfAvailable(this);
removeOldValues(preferences);
}

public void setUpConditionCallbacks(SharedPreferences preferences) {
initNetworkCallback();
initBeaconCallback(preferences);
}

private void initLogger() {
Expand Down Expand Up @@ -78,13 +97,33 @@ private void initBeaconManager() {
beaconParsers.add(
new BeaconParser("Eddystone UID")
.setBeaconLayout(BeaconParser.EDDYSTONE_UID_LAYOUT));
}
}

private void initNetworkCallback() {
// for older versions, we are good with the broadcast receiver configured in the manifest
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ConnectivityManager connectivityManager =
(ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
if (connectivityManager == null) {
DatabaseLogger.e(
TAG, "Unable to get system services, cannot register network callback");
} else {
ConnectivityManager.NetworkCallback wifiCallback =
NetworkService.getWifiCallback(this);
if (currentCallback.compareAndSet(null, wifiCallback)) {
DatabaseLogger.i(TAG, "Registering callback to await Wi-Fi connection");
NetworkRequest request = new NetworkRequest.Builder().build();
connectivityManager.registerNetworkCallback(request, wifiCallback);
}
}
}
}

SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);
Set<String> beacons =
sharedPreferences.getStringSet(BEACON_LIST, Collections.emptySet());
private void initBeaconCallback(SharedPreferences preferences) {
if (supportsBeacons()) {
Set<String> beacons = preferences.getStringSet(BEACON_LIST, Collections.emptySet());
if (!beacons.isEmpty()) {
DatabaseLogger.i(TAG, "Enabling beacon scanning for " + beacons);
PresenceBeaconManager.getInstance().initialize(this);
} else {
DatabaseLogger.i(
Expand All @@ -93,15 +132,37 @@ private void initBeaconManager() {
}
}

private void initClientId() {
SharedPreferences preference = PreferenceManager.getDefaultSharedPreferences(this);
if (!preference.contains(MQTT_CLIENT_ID)) {
private void initClientId(SharedPreferences preferences) {
if (!preferences.contains(MQTT_CLIENT_ID)) {
DatabaseLogger.i(TAG, "Generating persistent client ID");
preference.edit().putString(MQTT_CLIENT_ID, UUID.randomUUID().toString()).apply();
preferences.edit().putString(MQTT_CLIENT_ID, UUID.randomUUID().toString()).apply();
}
}

public void removeConditionCallbacks() {
removeNetworkCallback();
PresenceBeaconManager.getInstance().disableScanning();
}

private void removeNetworkCallback() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ConnectivityManager connectivityManager =
(ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
DatabaseLogger.i(TAG, "Removing callback to await Wi-Fi connection");
ConnectivityManager.NetworkCallback oldCallback = currentCallback.getAndSet(null);
if (oldCallback != null) {
connectivityManager.unregisterNetworkCallback(oldCallback);
}
}
}
}

public boolean supportsBeacons() {
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}

private void removeOldValues(SharedPreferences preferences) {
preferences.edit().remove(USE_WORKER_1).apply();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ protected CreateSchedule(MainActivity activity, Queue<HandlerFactory> handlerCha
@Override
protected void doInitialize() {
DatabaseLogger.i(TAG, "Ensure schedule is active");
new Scheduler(activity).ensureSchedule();
new Thread(() -> new Scheduler(activity).ensureSchedule()).start();
finishInitialization();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ protected EnsureBackgroundLocationPermission(

@Override
protected void doInitialize() {
// when we reach this point, the user has already successfully passed the
// EnsureLocationPermission step, so we know we have consent
DatabaseLogger.i(getName(), "User gave consent to access location data.");
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext())
.edit()
.putBoolean(LOCATION_CONSENT, true)
.apply();
if (activity.isLocationPermissionNeeded()
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& ContextCompat.checkSelfPermission(
Expand Down Expand Up @@ -66,10 +73,6 @@ protected void doInitialize() {
@RequiresApi(api = Build.VERSION_CODES.Q)
private void onResult(Activity parent, boolean ok) {
if (ok) {
PreferenceManager.getDefaultSharedPreferences(parent)
.edit()
.putBoolean(LOCATION_CONSENT, true)
.apply();
getLauncher().launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
} else {
DatabaseLogger.i(getName(), "User did not give consent. Stopping any further actions.");
Expand All @@ -88,6 +91,10 @@ protected void doHandleResult(Boolean result) {
finishInitialization();
} else {
DatabaseLogger.w(TAG, "Background location not granted, stopping initialization");
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext())
.edit()
.putBoolean(LOCATION_CONSENT, false)
.apply();
}
}

Expand Down
Loading

0 comments on commit 30ba61a

Please sign in to comment.