9 Commits

14 changed files with 189 additions and 84 deletions

View File

@ -11,8 +11,8 @@ android {
compileSdkVersion 29 compileSdkVersion 29
buildToolsVersion '29.0.2' buildToolsVersion '29.0.2'
useLibrary 'org.apache.http.legacy' useLibrary 'org.apache.http.legacy'
versionCode 96 versionCode 99
versionName "1.6.21" versionName "1.6.23"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -42,7 +42,7 @@ android {
dimension "version" dimension "version"
// applicationIdSuffix ".googlePlay" // applicationIdSuffix ".googlePlay"
versionNameSuffix "-googlePlay" versionNameSuffix "-googlePlay"
targetSdkVersion 29 targetSdkVersion 30
} }
fdroidFlavor fdroidFlavor

View File

@ -0,0 +1,18 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.jens.automation2",
"variantName": "processGooglePlayFlavorReleaseResources",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 98,
"versionName": "1.6.22-googlePlay",
"outputFile": "app-googlePlayFlavor-release.apk"
}
]
}

View File

@ -53,11 +53,11 @@ public class ActivityMainPoi extends ActivityGeneric
@Override @Override
public void onClick(View v) public void onClick(View v)
{ {
if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainPoi.this)) // if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainPoi.this))
{ // {
Toast.makeText(ActivityMainPoi.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); // Toast.makeText(ActivityMainPoi.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show();
return; // return;
} // }
if(!ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationCoarse, ActivityMainPoi.this) || !ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationFine, ActivityMainPoi.this)) if(!ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationCoarse, ActivityMainPoi.this) || !ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationFine, ActivityMainPoi.this))
{ {

View File

@ -50,11 +50,11 @@ public class ActivityMainProfiles extends ActivityGeneric
@Override @Override
public void onClick(View v) public void onClick(View v)
{ {
if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainProfiles.this)) // if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainProfiles.this))
{ // {
Toast.makeText(ActivityMainProfiles.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); // Toast.makeText(ActivityMainProfiles.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show();
return; // return;
} // }
profileToEdit = null; profileToEdit = null;
Intent manageSpecificProfileIntent = new Intent (ActivityMainProfiles.this, ActivityManageProfile.class); Intent manageSpecificProfileIntent = new Intent (ActivityMainProfiles.this, ActivityManageProfile.class);

View File

@ -52,11 +52,11 @@ public class ActivityMainRules extends ActivityGeneric
@Override @Override
public void onClick(View v) public void onClick(View v)
{ {
if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainRules.this)) // if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainRules.this))
{ // {
Toast.makeText(ActivityMainRules.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); // Toast.makeText(ActivityMainRules.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show();
return; // return;
} // }
ruleToEdit = null; ruleToEdit = null;
Intent startAddRuleIntent = new Intent(ActivityMainRules.this, ActivityManageRule.class); Intent startAddRuleIntent = new Intent(ActivityMainRules.this, ActivityManageRule.class);

View File

@ -33,6 +33,7 @@ public class ActivityPermissions extends Activity
public static final int requestCodeForPermissions = 12042; public static final int requestCodeForPermissions = 12042;
private static final int requestCodeForPermissionsWriteSettings = 12043; private static final int requestCodeForPermissionsWriteSettings = 12043;
private static final int requestCodeForPermissionsNotificationPolicy = 12044; private static final int requestCodeForPermissionsNotificationPolicy = 12044;
private static final int requestCodeForPermissionsBackgroundLocation = 12045;
protected String[] specificPermissionsToRequest = null; protected String[] specificPermissionsToRequest = null;
public static String intentExtraName = "permissionsToBeRequested"; public static String intentExtraName = "permissionsToBeRequested";
@ -280,8 +281,8 @@ public class ActivityPermissions extends Activity
addToArrayListUnique("android.permission.RECEIVE_BOOT_COMPLETED", requiredPermissions); addToArrayListUnique("android.permission.RECEIVE_BOOT_COMPLETED", requiredPermissions);
*/ */
if (!havePermission("android.permission.WRITE_EXTERNAL_STORAGE", workingContext)) // if (!havePermission(ActivityPermissions.writeExternalStoragePermissionName, workingContext))
addToArrayListUnique("android.permission.WRITE_EXTERNAL_STORAGE", requiredPermissions); // addToArrayListUnique(ActivityPermissions.writeExternalStoragePermissionName, requiredPermissions);
for(Profile profile : Profile.getProfileCollection()) for(Profile profile : Profile.getProfileCollection())
{ {
@ -768,6 +769,16 @@ public class ActivityPermissions extends Activity
} }
} }
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
if (requestCode == requestCodeForPermissionsBackgroundLocation)
{
NotificationManager mNotificationManager = (NotificationManager) ActivityPermissions.this.getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager.isNotificationPolicyAccessGranted())
requestPermissions(cachedPermissionsToRequest, true);
}
}
} }
} }
@ -827,6 +838,15 @@ public class ActivityPermissions extends Activity
startActivityForResult(intent, requestCodeForPermissionsNotificationPolicy); startActivityForResult(intent, requestCodeForPermissionsNotificationPolicy);
return; return;
} }
// else if (s.equalsIgnoreCase(permissionNameLocationBackground) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
// {
// requiredPermissions.remove(s);
// cachedPermissionsToRequest = requiredPermissions;
// Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
// intent.setData(Uri.parse("package:" + getPackageName()));
// startActivityForResult(intent, requestCodeForPermissionsBackgroundLocation);
// return;
// }
} }
} }

View File

@ -106,17 +106,17 @@ public class AutomationService extends Service implements OnInitListener
public boolean checkStartupRequirements(Context context, boolean startAtBoot) public boolean checkStartupRequirements(Context context, boolean startAtBoot)
{ {
if (!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, AutomationService.this)) // if (!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, AutomationService.this))
{ // {
/* // /*
Don't have permission to access external storage. This is a show stopper as // Don't have permission to access external storage. This is a show stopper as
the configuration file is stored on external storage. // the configuration file is stored on external storage.
*/ // */
Miscellaneous.logEvent("e", "Permission", "Don't have permission to access external storage. Will request it now.", 4); // Miscellaneous.logEvent("e", "Permission", "Don't have permission to access external storage. Will request it now.", 4);
// Toast.makeText(AutomationService.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); //// Toast.makeText(AutomationService.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show();
ActivityPermissions.requestSpecificPermission(ActivityPermissions.writeExternalStoragePermissionName); // ActivityPermissions.requestSpecificPermission(ActivityPermissions.writeExternalStoragePermissionName);
return false; // return false;
} // }
if(Build.VERSION.SDK_INT >= 28) if(Build.VERSION.SDK_INT >= 28)
{ {

View File

@ -21,9 +21,6 @@ import android.provider.MediaStore;
import android.provider.Settings.Secure; import android.provider.Settings.Secure;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.jens.automation2.location.LocationProvider; import com.jens.automation2.location.LocationProvider;
import com.jens.automation2.receivers.PhoneStatusListener; import com.jens.automation2.receivers.PhoneStatusListener;
@ -47,11 +44,14 @@ import org.xml.sax.SAXException;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader; import java.io.StringReader;
import java.lang.Thread.UncaughtExceptionHandler; import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -81,6 +81,8 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import androidx.core.app.NotificationCompat;
import static com.jens.automation2.AutomationService.NOTIFICATION_CHANNEL_ID; import static com.jens.automation2.AutomationService.NOTIFICATION_CHANNEL_ID;
import static com.jens.automation2.AutomationService.channelName; import static com.jens.automation2.AutomationService.channelName;
@ -313,6 +315,18 @@ public class Miscellaneous extends Service
{ {
if(writeableFolderStringCache == null) if(writeableFolderStringCache == null)
{ {
// Use the app-specific folder as new default.
writeableFolderStringCache = Miscellaneous.getAnyContext().getFilesDir().getAbsolutePath();
File newConfigFile = new File(writeableFolderStringCache + "/" + XmlFileInterface.settingsFileName);
migration:
if (!newConfigFile.exists())
{
if (ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, Miscellaneous.getAnyContext()))
{
// We have the storage permission, probably because it's an old installation. Files should be migrated to app-specific folder.
String testPath = null; String testPath = null;
File folder = null; File folder = null;
@ -328,29 +342,46 @@ public class Miscellaneous extends Service
for (String f : foldersToTestArray) for (String f : foldersToTestArray)
{ {
if (testFolder(f)) // if (testFolder(f))
{ // {
String pathToUse = f + "/" + Settings.folderName; String pathToUse = f + "/" + Settings.folderName;
Miscellaneous.logEvent("i", "Path", "Using " + pathToUse + " to store settings and log.", 2);
// Toast.makeText(getAnyContext(), "Using " + pathToUse + " to store settings and log.", Toast.LENGTH_LONG).show(); // Toast.makeText(getAnyContext(), "Using " + pathToUse + " to store settings and log.", Toast.LENGTH_LONG).show();
return pathToUse; // Migrate existing files
File oldDirectory = new File(pathToUse);
File newDirectory = new File(writeableFolderStringCache);
File oldConfigFilePath = new File(pathToUse + "/" + XmlFileInterface.settingsFileName);
if (oldConfigFilePath.exists() && oldConfigFilePath.canWrite())
{
Miscellaneous.logEvent("i", "Path", "Found old path " + pathToUse + " for settings and logs. Migrating old files to new directory.", 2);
for (File fileToBeMoved : oldDirectory.listFiles())
{
File dstFile = new File(writeableFolderStringCache + "/" + fileToBeMoved.getName());
/*
For some stupid reason Android's file.moveTo can't move files between
mount points. That's why we have to copy it and delete the src if successful.
*/
if(copyFileUsingStream(fileToBeMoved, dstFile))
fileToBeMoved.delete();
} }
else
Miscellaneous.logEvent("e", "getWritableFolder", folder.getAbsolutePath() + " does not exist and could not be created.", 3); String message = String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.filesHaveBeenMovedTo), newDirectory.getAbsolutePath());
Miscellaneous.writeStringToFile(oldDirectory.getAbsolutePath() + "/readme.txt", message);
break migration;
} }
// }
} }
catch(Exception e) } catch (Exception e)
{ {
Log.w("getWritableFolder", folder + " not writable."); Log.w("getWritableFolder", folder + " not writable.");
} }
// do not change to logEvent() - we can't write
Toast.makeText(getAnyContext(), "No writable folder could be found.", Toast.LENGTH_LONG).show();
Log.e("getWritableFolder", "No writable folder could be found.");
return null;
} }
else }
}
return writeableFolderStringCache; return writeableFolderStringCache;
} }
@ -1037,4 +1068,32 @@ public class Miscellaneous extends Service
return null; return null;
} }
} }
public static boolean copyFileUsingStream(File source, File dest) throws IOException
{
boolean returnValue = false;
InputStream is = null;
OutputStream os = null;
try
{
is = new FileInputStream(source);
os = new FileOutputStream(dest);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0)
{
os.write(buffer, 0, length);
}
returnValue = true;
}
finally
{
is.close();
os.close();
}
return returnValue;
}
} }

View File

@ -10,10 +10,8 @@ import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap; import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.File; import java.io.File;
import java.lang.reflect.Array;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
@ -72,7 +70,7 @@ public class News
String filePath = context.getFilesDir() + "/appNews.xml"; String filePath = context.getFilesDir() + "/appNews.xml";
if (!(new File(filePath)).exists() || Settings.lastNewsPolltime == -1 || now.getTimeInMillis() >= Settings.lastNewsPolltime + (long)(Settings.pollNewsEveryXDays * 24 * 60 * 60 * 1000)) if (!(new File(filePath)).exists() || Settings.lastNewsPolltime == -1 || now.getTimeInMillis() >= Settings.lastNewsPolltime + (long)(Settings.newsDisplayForXDays * 24 * 60 * 60 * 1000))
{ {
String newsUrl = "https://server47.de/automation/appNews.php"; String newsUrl = "https://server47.de/automation/appNews.php";
newsContent = Miscellaneous.downloadURL(newsUrl, null, null); newsContent = Miscellaneous.downloadURL(newsUrl, null, null);
@ -245,7 +243,7 @@ public class News
try try
{ {
Calendar limit = Calendar.getInstance(); Calendar limit = Calendar.getInstance();
limit.add(Calendar.DAY_OF_MONTH, -Settings.pollNewsEveryXDays); limit.add(Calendar.DAY_OF_MONTH, -Settings.newsPollEveryXDays);
return downloadNews(contexts[0], limit); return downloadNews(contexts[0], limit);
} }
catch(Exception e) catch(Exception e)

View File

@ -56,7 +56,7 @@ public class ReceiverCoordinator
} }
catch (ClassNotFoundException e) catch (ClassNotFoundException e)
{ {
e.printStackTrace(); // e.printStackTrace();
allImplementers = new Class[] { allImplementers = new Class[] {
AlarmListener.class, AlarmListener.class,

View File

@ -12,7 +12,8 @@ public class Settings implements SharedPreferences
{ {
public static final int rulesThatHaveBeenRanHistorySize = 10; public static final int rulesThatHaveBeenRanHistorySize = 10;
public final static int lockSoundChangesInterval = 15; public final static int lockSoundChangesInterval = 15;
public static final int pollNewsEveryXDays = 7; public static final int newsPollEveryXDays = 3;
public static final int newsDisplayForXDays = 3;
public static final String folderName = "Automation"; public static final String folderName = "Automation";
public static long minimumDistanceChangeForGpsUpdate; public static long minimumDistanceChangeForGpsUpdate;
@ -106,7 +107,7 @@ public class Settings implements SharedPreferences
protected static final boolean default_privacyLocationing = false; protected static final boolean default_privacyLocationing = false;
protected static final int default_startScreen = 0; protected static final int default_startScreen = 0;
protected static final boolean default_executeRulesAndProfilesWithSingleClick = false; protected static final boolean default_executeRulesAndProfilesWithSingleClick = false;
protected static final boolean default_displayNewsOnMainScreen = true; protected static final boolean default_displayNewsOnMainScreen = false;
protected static final boolean default_lockSoundChanges = false; protected static final boolean default_lockSoundChanges = false;
protected static final long default_lastNewsPolltime = -1; protected static final long default_lastNewsPolltime = -1;

View File

@ -25,7 +25,8 @@ import java.util.Collections;
public class XmlFileInterface public class XmlFileInterface
{ {
public static File settingsFile = new File(Miscellaneous.getWriteableFolder() + "/Automation_settings.xml"); public static String settingsFileName = "Automation_settings.xml";
public static File settingsFile = new File(Miscellaneous.getWriteableFolder() + "/" + settingsFileName);
public static Context context; public static Context context;
protected static final String encryptionKey = "Y1vsP12L2S3NkTJbDOR4bQ6i02hsoo"; protected static final String encryptionKey = "Y1vsP12L2S3NkTJbDOR4bQ6i02hsoo";
@ -343,18 +344,23 @@ public class XmlFileInterface
} }
public static void readFile() throws FileNotFoundException public static void readFile() throws FileNotFoundException
{
if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, Miscellaneous.getAnyContext()))
{ {
/* /*
Don't have permission to access external storage. This is a show stopper as Storage location has been moved to app-specific folder in Android/data
the configuration file is stored on external storage. Hence this permission is not requested any more. If it is already granted we assume the files are on /sdcard or similar.
Migration to app-specific folder has yet to be implemented.
*/ */
Miscellaneous.logEvent("e", "Permission", "Don't have permission to access external storage. Will request it now.", 4); // if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, Miscellaneous.getAnyContext()))
Toast.makeText(Miscellaneous.getAnyContext(), Miscellaneous.getAnyContext().getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); // {
ActivityPermissions.requestSpecificPermission(ActivityPermissions.writeExternalStoragePermissionName); // /*
return; // Don't have permission to access external storage. This is a show stopper as
} // the configuration file is stored on external storage.
// */
// Miscellaneous.logEvent("e", "Permission", "Don't have permission to access external storage. Will request it now.", 4);
// Toast.makeText(Miscellaneous.getAnyContext(), Miscellaneous.getAnyContext().getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show();
// ActivityPermissions.requestSpecificPermission(ActivityPermissions.writeExternalStoragePermissionName);
// return;
// }
/* /*
If we are here it may be that we just got permission to read storage. We need to check for the If we are here it may be that we just got permission to read storage. We need to check for the

View File

@ -594,4 +594,5 @@
<string name="deviceDoesNotHaveBluetooth">Dieses Gerät scheint kein Bluetooth zu haben. Sie können mit der Konfiguration fortfahren, aber es wird vermutlich keinen Effekt haben.</string> <string name="deviceDoesNotHaveBluetooth">Dieses Gerät scheint kein Bluetooth zu haben. Sie können mit der Konfiguration fortfahren, aber es wird vermutlich keinen Effekt haben.</string>
<string name="manageLocations">Orte anlegen oder ändern</string> <string name="manageLocations">Orte anlegen oder ändern</string>
<string name="publishedOn">veröffentlicht am</string> <string name="publishedOn">veröffentlicht am</string>
<string name="filesHaveBeenMovedTo">Automation benutzt jetzt ein anderes Verzeichnis, um Ihre Daten zu speichern. Alle Ihre Automation-Dateien wurden hierhin verschoben: \"%s\". Die Berechtigung für den externen Speicher wird nun nicht mehr benötigt; Sie können Sie entfernen. In einer künftigen Version wird sie entfernt werden.</string>
</resources> </resources>

View File

@ -594,7 +594,8 @@
<string name="startScreen">Start screen</string> <string name="startScreen">Start screen</string>
<string name="startScreenSummary">Select the screen the applications opens withs at start.</string> <string name="startScreenSummary">Select the screen the applications opens withs at start.</string>
<string name="executeRulesAndProfilesWithSingleClickTitle">Run rules/profiles with single click.</string> <string name="executeRulesAndProfilesWithSingleClickTitle">Run rules/profiles with single click.</string>
<string name="googleLocationChicanery">This app collects location data to determine if you\'re currently at one of the locations you created. Furthermore it is used to determine your current speed if you are using that trigger in rules. That is done even when the app is closed or not in use (but only when the service is activated).</string> <string name="googleLocationChicanery">This app collects location data to enable location based rules and speed detection even when the app is closed or not in use.</string>
<string name="googleLocationChicaneryOld">This app collects location data to determine if you\'re currently at one of the locations you created. Furthermore it is used to determine your current speed if you are using that trigger in rules. That is done even when the app is closed or not in use (but only when the service is activated).</string>
<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>
@ -604,4 +605,5 @@
<string name="publishedOn">published on</string> <string name="publishedOn">published on</string>
<string name="displayNewsOnMainScreen">Display application news on main screen</string> <string name="displayNewsOnMainScreen">Display application news on main screen</string>
<string name="displayNewsOnMainScreenDescription">Announcements about this app only, we\'re probably talking about 1-2 per year, not more.</string> <string name="displayNewsOnMainScreenDescription">Announcements about this app only, we\'re probably talking about 1-2 per year, not more.</string>
<string name="filesHaveBeenMovedTo">Automation now uses another path to store your files. All your Automation-files have been moved here: \"%s\". The external storage permission is not required anymore; you can revoke it. It will be removed in a future version.</string>
</resources> </resources>