Skip to content

Commit

Permalink
introduce explicit location consent setting
Browse files Browse the repository at this point in the history
this should better fulfill google's requirement for
background permission handling
  • Loading branch information
ostrya committed Nov 8, 2020
1 parent 93c41a9 commit cdad26b
Show file tree
Hide file tree
Showing 15 changed files with 179 additions and 35 deletions.
1 change: 0 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
android:label="@string/app_name"
android:name=".PresencePublisher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
Expand Down
16 changes: 14 additions & 2 deletions app/src/main/java/org/ostrya/presencepublisher/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.util.Collections;

import static org.ostrya.presencepublisher.ui.preference.about.LocationConsentPreference.LOCATION_CONSENT;
import static org.ostrya.presencepublisher.ui.preference.condition.AddBeaconChoicePreferenceDummy.BEACON_LIST;
import static org.ostrya.presencepublisher.ui.preference.condition.AddNetworkChoicePreferenceDummy.SSID_LIST;
import static org.ostrya.presencepublisher.ui.preference.condition.OfflineContentPreference.OFFLINE_CONTENT;
Expand Down Expand Up @@ -62,8 +63,6 @@ public void onCreate(Bundle savedInstanceState) {
|| Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);

handler.initialize(this);
}

@Override
Expand Down Expand Up @@ -112,6 +111,9 @@ private void onSharedPreferenceChanged(SharedPreferences preferences, String key
case LAST_SUCCESS:
case NEXT_SCHEDULE:
break;
case LOCATION_CONSENT:
handleConsentChange();
break;
default:
if (key.startsWith(WIFI_CONTENT_PREFIX)) {
onChangedConnectionProperty(key);
Expand All @@ -125,4 +127,14 @@ private void onChangedConnectionProperty(String key) {
HyperLog.i(TAG, "Changed parameter " + key);
new Scheduler(this).scheduleNow();
}

private void handleConsentChange() {
if (sharedPreferences.getBoolean(LOCATION_CONSENT, false)) {
HyperLog.i(TAG, "User consented to location access, initializing.");
handler.initialize(this);
} else {
HyperLog.i(TAG, "User revoked location access consent, stopping schedule.");
new Scheduler(this).stopSchedule();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.net.NetworkCapabilities.*;
import static org.ostrya.presencepublisher.PresencePublisher.*;
import static org.ostrya.presencepublisher.ui.preference.about.LocationConsentPreference.LOCATION_CONSENT;
import static org.ostrya.presencepublisher.ui.preference.condition.AddBeaconChoicePreferenceDummy.BEACON_LIST;
import static org.ostrya.presencepublisher.ui.preference.condition.SendOfflineMessagePreference.SEND_OFFLINE_MESSAGE;
import static org.ostrya.presencepublisher.ui.preference.condition.SendViaMobileNetworkPreference.SEND_VIA_MOBILE_NETWORK;
Expand All @@ -41,15 +42,19 @@ public class Scheduler {
private final Context applicationContext;
private final SharedPreferences sharedPreferences;
private final AlarmManager alarmManager;
private final PendingIntent scheduledIntent;
private final PendingIntent pendingAlarmIntent;
private final PendingIntent pendingNetworkIntent;

public Scheduler(Context context) {
applicationContext = context.getApplicationContext();
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext);
alarmManager = (AlarmManager) applicationContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(applicationContext, AlarmReceiver.class);
intent.setAction(ALARM_ACTION);
scheduledIntent = PendingIntent.getBroadcast(applicationContext, ALARM_PENDING_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Intent alarmIntent = new Intent(applicationContext, AlarmReceiver.class);
alarmIntent.setAction(ALARM_ACTION);
pendingAlarmIntent = PendingIntent.getBroadcast(applicationContext, ALARM_PENDING_INTENT_REQUEST_CODE, alarmIntent, FLAG_UPDATE_CURRENT);
Intent networkIntent = new Intent(applicationContext, ConnectivityBroadcastReceiver.class);
networkIntent.setAction(NETWORK_PENDING_INTENT_ACTION);
pendingNetworkIntent = PendingIntent.getBroadcast(applicationContext, NETWORK_PENDING_INTENT_REQUEST_CODE, networkIntent, FLAG_UPDATE_CURRENT);
}

public void scheduleNow() {
Expand All @@ -69,6 +74,19 @@ public void scheduleNext() {
messageScheduleInMinutes < 15);
}

public void stopSchedule() {
alarmManager.cancel(pendingAlarmIntent);
sharedPreferences.edit().remove(NEXT_SCHEDULE).apply();
NotificationManagerCompat.from(applicationContext).cancel(NOTIFICATION_ID);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ConnectivityManager connectivityManager = applicationContext.getSystemService(ConnectivityManager.class);
if (connectivityManager != null) {
connectivityManager.unregisterNetworkCallback(pendingNetworkIntent);
}
}

}

/**
* This is the counterpart to the broadcast receiver ConnectivityBroadcastReceiver registered in the manifest
* for Android 8+, where most implicit broadcasts are no longer delivered.
Expand Down Expand Up @@ -96,10 +114,7 @@ public void waitForNetworkReconnect() {
.addCapability(NET_CAPABILITY_FOREGROUND);
}
NetworkRequest networkRequest = requestBuilder.build();
Intent intent = new Intent(applicationContext, ConnectivityBroadcastReceiver.class);
intent.setAction(NETWORK_PENDING_INTENT_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(applicationContext, NETWORK_PENDING_INTENT_REQUEST_CODE, intent, FLAG_UPDATE_CURRENT);
connectivityManager.registerNetworkCallback(networkRequest, pendingIntent);
connectivityManager.registerNetworkCallback(networkRequest, pendingNetworkIntent);
}
sharedPreferences.edit().putLong(NEXT_SCHEDULE, WAITING_FOR_RECONNECT).apply();
NotificationManagerCompat.from(applicationContext)
Expand All @@ -117,23 +132,27 @@ private long getLastSuccess() {
}

private void scheduleFor(long nextSchedule, boolean ignoreBattery) {
if (!sharedPreferences.getBoolean(LOCATION_CONSENT, false)) {
HyperLog.w(TAG, "Location consent not given, will not schedule anything.");
return;
}
if (alarmManager == null) {
HyperLog.e(TAG, "Unable to get alarm manager, cannot schedule!");
return;
}
alarmManager.cancel(scheduledIntent);
alarmManager.cancel(pendingAlarmIntent);
HyperLog.i(TAG, "Next run at " + getFormattedTimestamp(applicationContext, nextSchedule));
sharedPreferences.edit().putLong(NEXT_SCHEDULE, nextSchedule).apply();
NotificationManagerCompat.from(applicationContext)
.notify(NOTIFICATION_ID, NotificationFactory.getNotification(applicationContext, getLastSuccess(), nextSchedule));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ignoreBattery) {
alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(nextSchedule, scheduledIntent), scheduledIntent);
alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(nextSchedule, pendingAlarmIntent), pendingAlarmIntent);
} else {
alarmManager.setAndAllowWhileIdle(RTC_WAKEUP, nextSchedule, scheduledIntent);
alarmManager.setAndAllowWhileIdle(RTC_WAKEUP, nextSchedule, pendingAlarmIntent);
}
} else {
alarmManager.set(RTC_WAKEUP, nextSchedule, scheduledIntent);
alarmManager.set(RTC_WAKEUP, nextSchedule, pendingAlarmIntent);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import androidx.preference.PreferenceScreen;
import org.ostrya.presencepublisher.R;
import org.ostrya.presencepublisher.ui.preference.about.BundledLicensesPreferenceDummy;
import org.ostrya.presencepublisher.ui.preference.about.LocationConsentPreference;
import org.ostrya.presencepublisher.ui.preference.about.PrivacyPreferenceDummy;
import org.ostrya.presencepublisher.ui.preference.about.SignaturePreferenceDummy;
import org.ostrya.presencepublisher.ui.preference.about.SourceRepositoryPreferenceDummy;
Expand All @@ -29,6 +30,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
legalCategory.addPreference(new SourceRepositoryPreferenceDummy(context, this));
legalCategory.addPreference(new BundledLicensesPreferenceDummy(context, this));
legalCategory.addPreference(new PrivacyPreferenceDummy(context, this));
legalCategory.addPreference(new LocationConsentPreference(context));

appCategory.addPreference(new VersionInfoPreferenceDummy(context));
appCategory.addPreference(new SignaturePreferenceDummy(context));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.HashSet;
import java.util.Set;

import static org.ostrya.presencepublisher.ui.preference.about.LocationConsentPreference.LOCATION_CONSENT;
import static org.ostrya.presencepublisher.ui.preference.condition.AddBeaconChoicePreferenceDummy.BEACON_LIST;
import static org.ostrya.presencepublisher.ui.preference.condition.AddNetworkChoicePreferenceDummy.SSID_LIST;
import static org.ostrya.presencepublisher.ui.preference.condition.BeaconPreference.BEACON_CONTENT_PREFIX;
Expand Down Expand Up @@ -96,9 +97,10 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {

setPreferenceScreen(screen);

getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);

offlineContent.setDependency(SEND_OFFLINE_MESSAGE);
screen.setEnabled(preference.getBoolean(LOCATION_CONSENT, false));

getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}

@Override
Expand All @@ -110,7 +112,9 @@ public void onPause() {
@Override
public void onResume() {
super.onResume();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
getPreferenceScreen().setEnabled(preferences.getBoolean(LOCATION_CONSENT, false));
preferences.registerOnSharedPreferenceChangeListener(listener);
}

private void onPreferencesChanged(SharedPreferences sharedPreferences, String key) {
Expand Down Expand Up @@ -141,6 +145,9 @@ private void onPreferencesChanged(SharedPreferences sharedPreferences, String ke
for (String remove : removed) {
beaconCategory.removePreferenceRecursively(BEACON_CONTENT_PREFIX + remove);
}
} else if (LOCATION_CONSENT.equals(key)) {
SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
getPreferenceScreen().setEnabled(preferences.getBoolean(LOCATION_CONSENT, false));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public void onPause() {
@Override
public void onResume() {
super.onResume();
lastSuccess.refresh();
nextSchedule.refresh();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class ConfirmationDialogFragment extends DialogFragment {
private int titleId;
private int messageId;
private String message;
private int confirmId = R.string.dialog_ok;
private int cancelId = R.string.dialog_cancel;

public static ConfirmationDialogFragment getInstance(final Callback callback, int titleId, int messageId) {
ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
Expand All @@ -31,14 +33,22 @@ public static ConfirmationDialogFragment getInstance(final Callback callback, in
return fragment;
}

public void setConfirmId(int confirmId) {
this.confirmId = confirmId;
}

public void setCancelId(int cancelId) {
this.cancelId = cancelId;
}

@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Activity parent = requireActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
builder.setTitle(titleId)
.setPositiveButton(R.string.dialog_ok, (dialog, id) -> callback.accept(parent, true))
.setNegativeButton(R.string.dialog_cancel, (dialog, id) -> callback.accept(parent, false));
.setPositiveButton(confirmId, (dialog, id) -> callback.accept(parent, true))
.setNegativeButton(cancelId, (dialog, id) -> callback.accept(parent, false));
if (message != null) {
builder.setMessage(message);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ protected void finishInitialization() {
inProgress.compareAndSet(true, false);
}

protected void cancelInitialization() {
inProgress.compareAndSet(true, false);
}

protected ActivityResultLauncher<I> getLauncher() {
return launcher;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected void doInitialize(MainActivity activity) {
}
ConfirmationDialogFragment fragment = ConfirmationDialogFragment.getInstance(this::onResult,
R.string.background_location_permission_dialog_title,
activity.getString(R.string.background_location_permission_dialog_message, activity.getString(R.string.app_name), optionName));
activity.getString(R.string.background_location_permission_dialog_message, optionName));
fragment.show(fm, null);
} else {
finishInitialization();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.ostrya.presencepublisher.ui.initialization;

import android.app.Activity;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
import com.hypertrack.hyperlog.HyperLog;
import org.ostrya.presencepublisher.MainActivity;
import org.ostrya.presencepublisher.R;
import org.ostrya.presencepublisher.ui.dialog.ConfirmationDialogFragment;

import java.util.Queue;

import static org.ostrya.presencepublisher.ui.preference.about.LocationConsentPreference.LOCATION_CONSENT;

public class EnsureLocationConsent extends AbstractChainedHandler<Void, Void> {
protected EnsureLocationConsent(Queue<HandlerFactory> handlerChain) {
super(null, handlerChain);
}

@Override
protected void doInitialize(MainActivity activity) {
if (activity.isLocationServiceNeeded() && !PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(LOCATION_CONSENT, false)) {
HyperLog.i(TAG, "Location consent not yet granted, asking user ...");
FragmentManager fm = activity.getSupportFragmentManager();

ConfirmationDialogFragment fragment = ConfirmationDialogFragment.getInstance(this::onResult,
R.string.location_consent_title,
activity.getString(R.string.location_consent_dialog_summary, activity.getString(R.string.tab_about_title), activity.getString(R.string.privacy_title), activity.getString(R.string.location_consent_title)));
fragment.setConfirmId(R.string.dialog_accept);
fragment.setCancelId(R.string.dialog_decline);
fragment.show(fm, null);
} else {
finishInitialization();
}
}

private void onResult(Activity parent, boolean ok) {
if (ok) {
PreferenceManager.getDefaultSharedPreferences(parent).edit()
.putBoolean(LOCATION_CONSENT, true)
.apply();
finishInitialization();
} else {
HyperLog.i(getName(), "User did not give consent. Stopping any further actions.");
cancelInitialization();
}
}

@Override
protected void doHandleResult(Void result) {
HyperLog.w(TAG, "Skipping unexpected result");
}

@Override
protected String getName() {
return "EnsureLocationConsent";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public interface InitializationHandler {
List<HandlerFactory> HANDLER_CHAIN =
Collections.unmodifiableList(Arrays.asList(
EnsureLocationConsent::new,
EnsureLocationPermission::new,
EnsureBackgroundLocationPermission::new,
EnsureLocationServiceEnabled::new,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ostrya.presencepublisher.ui.preference.about;

import android.content.Context;
import org.ostrya.presencepublisher.R;
import org.ostrya.presencepublisher.ui.preference.common.BooleanPreferenceBase;

public class LocationConsentPreference extends BooleanPreferenceBase {
public static final String LOCATION_CONSENT = "locationConsent";

public LocationConsentPreference(Context context) {
super(context, LOCATION_CONSENT, R.string.location_consent_title, R.string.location_consent_summary);
}
}
14 changes: 11 additions & 3 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@
Namen des aktuell verbundenen WLANs nicht bestimmen und Bluetooth-Beacons nicht finden.
</string>
<string name="background_location_permission_dialog_title">Standortzugriff im Hintergrund</string>
<string name="background_location_permission_dialog_message" formatted="true">%1$s erfasst Standortdaten, um den aktuell verbundenen WLAN-Namen zu erfahren und nahegelegene Bluetooth-Beacons zu finden, auch wenn die App geschlossen ist oder nicht verwendet wird.%n%n
Bitte erlaube dies, indem du im folgenden Dialog \'%2$s\' auswählst. Andernfalls kann diese App keine Anwesenheitsnachrichten senden.
</string>
<string name="background_location_permission_dialog_message" formatted="true">Bitte erlaube den Zugriff auf deine Standortdaten im Hintergrund, indem du im folgenden Dialog \'%1$s\' auswählst. Andernfalls kann diese App keine Anwesenheitsnachrichten senden.</string>
<!-- only needed for Android 10, where the BACKGROUND_LOCATION permission is introduced, but the option name is not exposed via PackageManager#getBackgroundPermissionOptionLabel() -->
<string name="background_location_permission_option_name">Immer zulassen</string>
<string name="location_consent_title">Zugriff auf Standortdaten</string>
<string name="location_consent_dialog_summary" formatted="true">
Diese App erhebt Standortdaten, um das Erkennen deiner Gegenwart zu Hause zu aktivieren, auch wenn die App geschlossen ist oder nicht verwendet wird.%n%n
Um deine Gegenwart zu erkennen, wertet diese App den Namen des verbundenen WLANs aus und prüft auf bekannte Bluetooth-Beacons in der Nähe.%n%n
Um mehr darüber zu erfahren, wie deine Standortdaten erfasst und verarbeitet werden, schau dir bitte \'%1$s\' > \'%2$s\' an.%n%n
Du kannst diese Zustimmung jederzeit geben oder zurückziehen, wenn du zu \'%1$s\' > \'%3$s\' navigierst."
</string>
<string name="location_consent_summary">Erlaube dieser App, deine Standortdaten zu erfassen und zu verarbeiten.</string>
<string name="bluetooth_dialog_title">Bluetooth</string>
<string name="bluetooth_dialog_message">Bitte schalte Bluetooth an, damit diese App nach Beacons suchen kann. Andernfalls
wird die App keine Beacons finden.
Expand Down Expand Up @@ -109,4 +115,6 @@
<string name="dialog_close">Schließen</string>
<string name="source_repo_summary">Tippe hier, um den Quellcode in einem Browser zu öffnen.</string>
<string name="privacy_url">https://github.com/ostrya/PresencePublisher/blob/master/legal/de/PRIVACY_POLICY.md</string>
<string name="dialog_accept">Zustimmen</string>
<string name="dialog_decline">Ablehnen</string>
</resources>
Loading

0 comments on commit cdad26b

Please sign in to comment.