All 3 flavors can be compiled.
This commit is contained in:
parent
8690dec2e2
commit
f379ab01ef
@ -40,7 +40,7 @@ android {
|
|||||||
googlePlayFlavor
|
googlePlayFlavor
|
||||||
{
|
{
|
||||||
dimension "version"
|
dimension "version"
|
||||||
applicationIdSuffix ".googlePlay"
|
// applicationIdSuffix ".googlePlay"
|
||||||
versionNameSuffix "-googlePlay"
|
versionNameSuffix "-googlePlay"
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ android {
|
|||||||
fdroidFlavor
|
fdroidFlavor
|
||||||
{
|
{
|
||||||
dimension "version"
|
dimension "version"
|
||||||
applicationIdSuffix ".fdroid"
|
// applicationIdSuffix ".fdroid"
|
||||||
versionNameSuffix "-fdroid"
|
versionNameSuffix "-fdroid"
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ android {
|
|||||||
apkFlavor
|
apkFlavor
|
||||||
{
|
{
|
||||||
dimension "version"
|
dimension "version"
|
||||||
applicationIdSuffix ".apk"
|
// applicationIdSuffix ".apk"
|
||||||
versionNameSuffix "-apk"
|
versionNameSuffix "-apk"
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
}
|
}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package com.jens.automation2;
|
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
|
||||||
import com.google.android.gms.common.GoogleApiAvailability;
|
|
||||||
|
|
||||||
public class MiscellaneousWithGoogleServices
|
|
||||||
{
|
|
||||||
public static boolean isPlayServiceAvailable()
|
|
||||||
{
|
|
||||||
GoogleApiAvailability aa = new GoogleApiAvailability();
|
|
||||||
if(aa.isGooglePlayServicesAvailable(Miscellaneous.getAnyContext()) == ConnectionResult.SUCCESS)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,6 @@ import android.util.Log;
|
|||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
import com.google.android.gms.common.GooglePlayServicesUtil;
|
import com.google.android.gms.common.GooglePlayServicesUtil;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
|
||||||
import com.google.android.gms.location.ActivityRecognition;
|
import com.google.android.gms.location.ActivityRecognition;
|
||||||
import com.google.android.gms.location.ActivityRecognitionApi;
|
import com.google.android.gms.location.ActivityRecognitionApi;
|
||||||
import com.google.android.gms.location.ActivityRecognitionResult;
|
import com.google.android.gms.location.ActivityRecognitionResult;
|
||||||
@ -199,6 +198,15 @@ public class ActivityDetectionReceiver extends IntentService implements Automati
|
|||||||
{
|
{
|
||||||
Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error stopping ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3);
|
Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error stopping ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPlayServiceAvailable()
|
||||||
|
{
|
||||||
|
if(GooglePlayServicesUtil.isGooglePlayServicesAvailable(Miscellaneous.getAnyContext()) == ConnectionResult.SUCCESS)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package com.jens.automation2;
|
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
|
||||||
import com.google.android.gms.common.GoogleApiAvailability;
|
|
||||||
|
|
||||||
public class MiscellaneousWithGoogleServices
|
|
||||||
{
|
|
||||||
public static boolean isPlayServiceAvailable()
|
|
||||||
{
|
|
||||||
GoogleApiAvailability aa = new GoogleApiAvailability();
|
|
||||||
if(aa.isGooglePlayServicesAvailable(Miscellaneous.getAnyContext()) == ConnectionResult.SUCCESS)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,6 +47,8 @@ import java.util.Collections;
|
|||||||
|
|
||||||
public class ActivityManageSpecificRule extends Activity
|
public class ActivityManageSpecificRule extends Activity
|
||||||
{
|
{
|
||||||
|
final static String activityDetectionClassPath = "com.jens.automation2.receivers.ActivityDetectionReceiver";
|
||||||
|
|
||||||
public Context context;
|
public Context context;
|
||||||
private Button cmdTriggerAdd, cmdActionAdd, cmdSaveRule;
|
private Button cmdTriggerAdd, cmdActionAdd, cmdSaveRule;
|
||||||
private ListView triggerListView, actionListView;
|
private ListView triggerListView, actionListView;
|
||||||
@ -536,7 +538,7 @@ public class ActivityManageSpecificRule extends Activity
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Method m = Miscellaneous.getClassMethodReflective("ActivityDetectionReceiver", "isPlayServiceAvailable");
|
Method m = Miscellaneous.getClassMethodReflective(activityDetectionClassPath, "isPlayServiceAvailable");
|
||||||
if(m != null)
|
if(m != null)
|
||||||
{
|
{
|
||||||
boolean available = (Boolean)m.invoke(null);
|
boolean available = (Boolean)m.invoke(null);
|
||||||
@ -548,6 +550,8 @@ public class ActivityManageSpecificRule extends Activity
|
|||||||
else
|
else
|
||||||
Toast.makeText(myContext, getResources().getString(R.string.triggerOnlyAvailableIfPlayServicesInstalled), Toast.LENGTH_LONG).show();
|
Toast.makeText(myContext, getResources().getString(R.string.triggerOnlyAvailableIfPlayServicesInstalled), Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
Miscellaneous.messageBox(getResources().getString(R.string.error), getResources().getString(R.string.featureNotInFdroidVersion), ActivityManageSpecificRule.this).show();
|
||||||
}
|
}
|
||||||
catch (IllegalAccessException | InvocationTargetException e)
|
catch (IllegalAccessException | InvocationTargetException e)
|
||||||
{
|
{
|
||||||
@ -921,7 +925,7 @@ public class ActivityManageSpecificRule extends Activity
|
|||||||
|
|
||||||
alertDialog.setTitle(Miscellaneous.getAnyContext().getResources().getString(R.string.selectTypeOfActivity));
|
alertDialog.setTitle(Miscellaneous.getAnyContext().getResources().getString(R.string.selectTypeOfActivity));
|
||||||
|
|
||||||
Method m = Miscellaneous.getClassMethodReflective("ActivityDetectionReceiver", "getAllDescriptions");
|
Method m = Miscellaneous.getClassMethodReflective(activityDetectionClassPath, "getAllDescriptions");
|
||||||
if(m != null)
|
if(m != null)
|
||||||
{
|
{
|
||||||
String[] choices = new String[0];
|
String[] choices = new String[0];
|
||||||
@ -933,12 +937,12 @@ public class ActivityManageSpecificRule extends Activity
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which)
|
public void onClick(DialogInterface dialog, int which)
|
||||||
{
|
{
|
||||||
Method m = Miscellaneous.getClassMethodReflective("ActivityDetectionReceiver", "getAllTypes");
|
Method m = Miscellaneous.getClassMethodReflective(activityDetectionClassPath, "getAllTypes");
|
||||||
if(m != null)
|
if(m != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Integer[] choices = (Integer[])m.invoke(null);
|
int[] choices = (int[])m.invoke(null);
|
||||||
|
|
||||||
newTrigger.setActivityDetectionType(choices[which]);
|
newTrigger.setActivityDetectionType(choices[which]);
|
||||||
ruleToEdit.getTriggerSet().add(newTrigger);
|
ruleToEdit.getTriggerSet().add(newTrigger);
|
||||||
|
@ -48,6 +48,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
@ -885,13 +886,13 @@ public class Miscellaneous extends Service
|
|||||||
|
|
||||||
public static Method getClassMethodReflective(String className, String methodName)
|
public static Method getClassMethodReflective(String className, String methodName)
|
||||||
{
|
{
|
||||||
Class atRecClass = null;
|
Class foundClass = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
atRecClass = Class.forName("ActivityDetectionReceiver");
|
foundClass = Class.forName(className);
|
||||||
for(Method m : atRecClass.getMethods())
|
for(Method m : foundClass.getDeclaredMethods())
|
||||||
{
|
{
|
||||||
if(m.getName().equalsIgnoreCase("isPlayServiceAvailable"))
|
if(m.getName().equalsIgnoreCase(methodName))
|
||||||
{
|
{
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
@ -904,4 +905,27 @@ public class Miscellaneous extends Service
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Object runMethodReflective(String className, String methodName, Object[] params)
|
||||||
|
{
|
||||||
|
Method m = getClassMethodReflective(className, methodName);
|
||||||
|
Object result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(params == null)
|
||||||
|
result = m.invoke((Object[]) null);
|
||||||
|
else
|
||||||
|
result = m.invoke(null, params);
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package com.jens.automation2;
|
package com.jens.automation2;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.jens.automation2.location.CellLocationChangedReceiver;
|
import com.jens.automation2.location.CellLocationChangedReceiver;
|
||||||
@ -15,6 +16,13 @@ import com.jens.automation2.receivers.PhoneStatusListener;
|
|||||||
import com.jens.automation2.receivers.ProcessListener;
|
import com.jens.automation2.receivers.ProcessListener;
|
||||||
import com.jens.automation2.receivers.TimeZoneListener;
|
import com.jens.automation2.receivers.TimeZoneListener;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import static com.jens.automation2.ActivityManageSpecificRule.activityDetectionClassPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by jens on 08.03.2017.
|
* Created by jens on 08.03.2017.
|
||||||
*/
|
*/
|
||||||
@ -28,28 +36,58 @@ public class ReceiverCoordinator
|
|||||||
* - Accelerometer
|
* - Accelerometer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static final Class[] allImplementers = {
|
public static Class[] allImplementers;
|
||||||
ActivityDetectionReceiver.class,
|
|
||||||
AlarmListener.class,
|
static void fillImplementers()
|
||||||
BatteryReceiver.class,
|
{
|
||||||
BluetoothReceiver.class,
|
try
|
||||||
ConnectivityReceiver.class,
|
{
|
||||||
HeadphoneJackListener.class,
|
Class adClass = Class.forName("ActivityDetectionReceiver");
|
||||||
//NfcReceiver.class,
|
allImplementers = new Class[] {
|
||||||
NoiseListener.class,
|
adClass,
|
||||||
PhoneStatusListener.class,
|
AlarmListener.class,
|
||||||
ProcessListener.class,
|
BatteryReceiver.class,
|
||||||
TimeZoneListener.class
|
BluetoothReceiver.class,
|
||||||
};
|
ConnectivityReceiver.class,
|
||||||
|
HeadphoneJackListener.class,
|
||||||
|
//NfcReceiver.class,
|
||||||
|
NoiseListener.class,
|
||||||
|
PhoneStatusListener.class,
|
||||||
|
ProcessListener.class,
|
||||||
|
TimeZoneListener.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
allImplementers = new Class[] {
|
||||||
|
AlarmListener.class,
|
||||||
|
BatteryReceiver.class,
|
||||||
|
BluetoothReceiver.class,
|
||||||
|
ConnectivityReceiver.class,
|
||||||
|
HeadphoneJackListener.class,
|
||||||
|
//NfcReceiver.class,
|
||||||
|
NoiseListener.class,
|
||||||
|
PhoneStatusListener.class,
|
||||||
|
ProcessListener.class,
|
||||||
|
TimeZoneListener.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static AutomationListenerInterface[] listeners = null;
|
private static AutomationListenerInterface[] listeners = null;
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||||
public static void startAllReceivers()
|
public static void startAllReceivers()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* New procedure:
|
* New procedure:
|
||||||
* Save instances of Listeners in ArrayList and run them.
|
* Save instances of Listeners in ArrayList and run them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
fillImplementers();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if(listeners == null)
|
if(listeners == null)
|
||||||
@ -130,7 +168,10 @@ public class ReceiverCoordinator
|
|||||||
|
|
||||||
//startActivityDetectionReceiver
|
//startActivityDetectionReceiver
|
||||||
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.activityDetection))
|
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.activityDetection))
|
||||||
ActivityDetectionReceiver.startActivityDetectionReceiver();
|
{
|
||||||
|
Miscellaneous.runMethodReflective(activityDetectionClassPath, "startActivityDetectionReceiver", null);
|
||||||
|
// ActivityDetectionReceiver.startActivityDetectionReceiver();
|
||||||
|
}
|
||||||
|
|
||||||
//startBluetoothReceiver
|
//startBluetoothReceiver
|
||||||
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.bluetoothConnection))
|
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.bluetoothConnection))
|
||||||
@ -153,7 +194,8 @@ public class ReceiverCoordinator
|
|||||||
AlarmListener.stopAlarmListener(AutomationService.getInstance());
|
AlarmListener.stopAlarmListener(AutomationService.getInstance());
|
||||||
NoiseListener.stopNoiseListener();
|
NoiseListener.stopNoiseListener();
|
||||||
ProcessListener.stopProcessListener(AutomationService.getInstance());
|
ProcessListener.stopProcessListener(AutomationService.getInstance());
|
||||||
ActivityDetectionReceiver.stopActivityDetectionReceiver();
|
Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "stopActivityDetectionReceiver", null);
|
||||||
|
// ActivityDetectionReceiver.stopActivityDetectionReceiver();
|
||||||
BluetoothReceiver.stopBluetoothReceiver();
|
BluetoothReceiver.stopBluetoothReceiver();
|
||||||
HeadphoneJackListener.getInstance().stopListener(AutomationService.getInstance());
|
HeadphoneJackListener.getInstance().stopListener(AutomationService.getInstance());
|
||||||
}
|
}
|
||||||
@ -213,25 +255,32 @@ public class ReceiverCoordinator
|
|||||||
|
|
||||||
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.activityDetection))
|
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.activityDetection))
|
||||||
{
|
{
|
||||||
if(ActivityDetectionReceiver.isActivityDetectionReceiverRunning())
|
boolean isRunning = (Boolean)Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "isActivityDetectionReceiverRunning", null);
|
||||||
|
if(isRunning)
|
||||||
{
|
{
|
||||||
Miscellaneous.logEvent("i", "LocationProvider", "Restarting ActivityDetectionReceiver because used in a new/changed rule.", 4);
|
Miscellaneous.logEvent("i", "LocationProvider", "Restarting ActivityDetectionReceiver because used in a new/changed rule.", 4);
|
||||||
if(ActivityDetectionReceiver.haveAllPermission())
|
boolean haveAllPerms = (Boolean)Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "haveAllPermission", null);
|
||||||
ActivityDetectionReceiver.restartActivityDetectionReceiver();
|
if(haveAllPerms)
|
||||||
|
Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "restartActivityDetectionReceiver", null);
|
||||||
|
// ActivityDetectionReceiver.restartActivityDetectionReceiver();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Miscellaneous.logEvent("i", "LocationProvider", "Starting ActivityDetectionReceiver because used in a new/changed rule.", 4);
|
Miscellaneous.logEvent("i", "LocationProvider", "Starting ActivityDetectionReceiver because used in a new/changed rule.", 4);
|
||||||
if(ActivityDetectionReceiver.haveAllPermission())
|
boolean haveAllPerms = (Boolean)Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "haveAllPermission", null);
|
||||||
ActivityDetectionReceiver.startActivityDetectionReceiver();
|
if(haveAllPerms)
|
||||||
|
Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "startActivityDetectionReceiver", null);
|
||||||
|
// ActivityDetectionReceiver.startActivityDetectionReceiver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(ActivityDetectionReceiver.isActivityDetectionReceiverRunning())
|
boolean isRunning = (Boolean)Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "isActivityDetectionReceiverRunning", null);
|
||||||
|
if(isRunning)
|
||||||
{
|
{
|
||||||
Miscellaneous.logEvent("i", "LocationProvider", "Shutting down ActivityDetectionReceiver because not used in any rule.", 4);
|
Miscellaneous.logEvent("i", "LocationProvider", "Shutting down ActivityDetectionReceiver because not used in any rule.", 4);
|
||||||
ActivityDetectionReceiver.stopActivityDetectionReceiver();
|
Miscellaneous.runMethodReflective("ActivityDetectionReceiver", "stopActivityDetectionReceiver", null);
|
||||||
|
// ActivityDetectionReceiver.stopActivityDetectionReceiver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,20 +356,20 @@ public class Trigger
|
|||||||
if(ActivityPermissions.isPermissionDeclaratedInManifest(Miscellaneous.getAnyContext(), "com.google.android.gms.permission.ACTIVITY_RECOGNITION"))
|
if(ActivityPermissions.isPermissionDeclaratedInManifest(Miscellaneous.getAnyContext(), "com.google.android.gms.permission.ACTIVITY_RECOGNITION"))
|
||||||
{
|
{
|
||||||
// This type doesn't have an activate/deactivate equivalent, at least not yet.
|
// This type doesn't have an activate/deactivate equivalent, at least not yet.
|
||||||
try
|
// try
|
||||||
{
|
// {
|
||||||
Class activityDetection = Class.forName("com.jens.automation2.receivers.ActivityDetectionReceiver");
|
returnString.append(Miscellaneous.runMethodReflective(ActivityManageSpecificRule.activityDetectionClassPath, "getDescription", new Object[] { getActivityDetectionType() } ));
|
||||||
for(Method method : activityDetection.getMethods())
|
// for(Method method : activityDetection.getMethods())
|
||||||
{
|
// {
|
||||||
if(method.getName().equalsIgnoreCase("getDescription"))
|
// if(method.getName().equalsIgnoreCase("getDescription"))
|
||||||
returnString.append(method.invoke(getActivityDetectionType()));
|
// returnString.append(method.invoke());
|
||||||
// returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivity) + " " + activityDetection.getDescription(getActivityDetectionType()));
|
//// returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivity) + " " + activityDetection.getDescription(getActivityDetectionType()));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e)
|
// catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e)
|
||||||
{
|
// {
|
||||||
e.printStackTrace();
|
// e.printStackTrace();
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -597,4 +597,6 @@
|
|||||||
<string name="android.permission.ACCESS_BACKGROUND_LOCATION">Read location in background.</string>
|
<string name="android.permission.ACCESS_BACKGROUND_LOCATION">Read location in background.</string>
|
||||||
<string name="deviceDoesNotHaveBluetooth">This device does not seem to have bluetooth. You can still continue configuring this, but it will most likely not have an effect.</string>
|
<string name="deviceDoesNotHaveBluetooth">This device does not seem to have bluetooth. You can still continue configuring this, but it will most likely not have an effect.</string>
|
||||||
<string name="manageLocations">Create or edit locations</string>
|
<string name="manageLocations">Create or edit locations</string>
|
||||||
|
<string name="error">Error</string>
|
||||||
|
<string name="featureNotInFdroidVersion">This feature is based on non-free software. Therefore is is not available in the F-Droid version.</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue
Block a user