package com.jens.automation2; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.documentfile.provider.DocumentFile; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.util.ArrayList; public class ActivityMaintenance extends Activity { final static int requestCodeImport = 1001; final static int requestCodeExport = 1002; final static int requestCodeMoreSettings = 6000; final static String prefsFileName = "com.jens.automation2_preferences.xml"; TextView tvFileStoreLocation, tvAppVersion; Button bVolumeTest, bMoreSettings, bSettingsSetToDefault, bShareConfigAndLog, bImportConfiguration, bExportConfiguration; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maintenance); bVolumeTest = (Button) findViewById(R.id.bVolumeTest); bVolumeTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(ActivityMaintenance.this, ActivityVolumeTest.class); startActivity(intent); } }); bShareConfigAndLog = (Button) findViewById(R.id.bShareConfigAndLog); bShareConfigAndLog.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getShareConfigAndLogDialogue(ActivityMaintenance.this).show(); } }); bSettingsSetToDefault = (Button) findViewById(R.id.bSettingsSetToDefault); bSettingsSetToDefault.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getDefaultSettingsDialog(ActivityMaintenance.this).show(); } }); Button bMoreSettings = (Button) findViewById(R.id.bMoreSettings); bMoreSettings.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent myIntent = new Intent(ActivityMaintenance.this, ActivitySettings.class); startActivityForResult(myIntent, requestCodeMoreSettings); } }); bImportConfiguration = (Button) findViewById(R.id.bImportConfiguration); bImportConfiguration.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(intent, requestCodeImport); } }); bExportConfiguration = (Button) findViewById(R.id.bExportConfiguration); bExportConfiguration.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(intent, requestCodeExport); } }); tvFileStoreLocation = (TextView)findViewById(R.id.tvFileStoreLocation); tvAppVersion = (TextView)findViewById(R.id.tvAppVersion); tvAppVersion.setText( "Version: " + BuildConfig.VERSION_NAME + Miscellaneous.lineSeparator + "Version code: " + String.valueOf(BuildConfig.VERSION_CODE) + Miscellaneous.lineSeparator + "Flavor: " + BuildConfig.FLAVOR ); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch(requestCode) { case requestCodeMoreSettings: //settings Settings.readFromPersistentStorage(this); if (AutomationService.isMyServiceRunning(this)) AutomationService.getInstance().serviceInterface(AutomationService.serviceCommands.reloadSettings); if (AutomationService.isMyServiceRunning(ActivityMaintenance.this)) Toast.makeText(this, getResources().getString(R.string.settingsWillTakeTime), Toast.LENGTH_LONG).show(); break; case requestCodeImport: if(resultCode == RESULT_OK) { Uri uriTree = data.getData(); importFiles(uriTree); } break; case requestCodeExport: if(resultCode == RESULT_OK) { Uri uriTree = data.getData(); exportFiles(uriTree); } break; } } void importFiles(Uri uriTree) { // https://stackoverflow.com/questions/46237558/android-strange-behavior-of-documentfile-inputstream File dstRules = new File(Miscellaneous.getWriteableFolder() + "/" + XmlFileInterface.settingsFileName); File dstPrefs = new File(Miscellaneous.getWriteableFolder() + "/../shared_prefs/" + prefsFileName); DocumentFile directory = DocumentFile.fromTreeUri(this, uriTree); int applicableFilesFound = 0; int filesImported = 0; if(directory.listFiles().length > 0) { for (DocumentFile file : directory.listFiles()) { if (file.getName().equals(XmlFileInterface.settingsFileName)) { applicableFilesFound++; if(file.canRead()) { // import rules, locations, etc. if (Miscellaneous.copyDocumentFileToFile(file, dstRules)) filesImported++; else Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.rulesImportError), Toast.LENGTH_LONG).show(); } } else if (file.getName().equals(prefsFileName)) { applicableFilesFound++; if(file.canRead()) { // import rules, locations, etc. if (Miscellaneous.copyDocumentFileToFile(file, dstPrefs)) filesImported++; else Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.prefsImportError), Toast.LENGTH_LONG).show(); } } } if(applicableFilesFound > 0) { if(filesImported == 0) Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.noFilesImported), Toast.LENGTH_LONG).show(); else if(filesImported < applicableFilesFound) Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.notAllFilesImported), Toast.LENGTH_LONG).show(); else if (filesImported == applicableFilesFound) { Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.configurationImportedSuccessfully), Toast.LENGTH_LONG).show(); try { XmlFileInterface.readFile(); ActivityMainPoi.getInstance().updateListView(); ActivityMainRules.getInstance().updateListView(); ActivityMainProfiles.getInstance().updateListView(); } catch (Exception e) { Miscellaneous.logEvent("e", "Reading import", "Rules re-read failed: " + Log.getStackTraceString(e), 1); Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.errorReadingPoisAndRulesFromFile), Toast.LENGTH_LONG).show(); } Settings.readFromPersistentStorage(ActivityMaintenance.this); } else Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.noFilesImported), Toast.LENGTH_LONG).show(); } else Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.noApplicableFilesFoundInDirectory), Toast.LENGTH_LONG).show(); } else Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.noApplicableFilesFoundInDirectory), Toast.LENGTH_LONG).show(); } void exportFiles(Uri uriTree) { DocumentFile directory = DocumentFile.fromTreeUri(this, uriTree); File srcRules = new File(Miscellaneous.getWriteableFolder() + "/" + XmlFileInterface.settingsFileName); File srcPrefs = new File(Miscellaneous.getWriteableFolder() + "/../shared_prefs/" + prefsFileName); // Clean up for(DocumentFile file : directory.listFiles()) { /* On some few users' devices it seems this caused a crash because file.getName() was null. The reason for that remains unknown, but we don't want the export to crash because of it. */ if(!StringUtils.isEmpty(file.getName())) { if (file.getName().equals(XmlFileInterface.settingsFileName) && file.canWrite()) file.delete(); else if (file.getName().equals(prefsFileName) && file.canWrite()) file.delete(); } } DocumentFile dstRules = directory.createFile("text/xml", XmlFileInterface.settingsFileName); DocumentFile dstPrefs = directory.createFile("text/xml", prefsFileName); if(dstRules.canWrite() && dstPrefs.canWrite()) { if(Miscellaneous.copyFileToDocumentFile(srcRules, dstRules) && Miscellaneous.copyFileToDocumentFile(srcPrefs, dstPrefs)) Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.configurationExportedSuccessfully), Toast.LENGTH_LONG).show(); else Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.ConfigurationExportError), Toast.LENGTH_LONG).show(); } else Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.ConfigurationExportError), Toast.LENGTH_LONG).show(); } private static AlertDialog getDefaultSettingsDialog(final Context context) { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setTitle(context.getResources().getString(R.string.areYouSure)); alertDialogBuilder.setPositiveButton(context.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (Settings.initializeSettings(context, true)) Toast.makeText(context, context.getResources().getString(R.string.settingsSetToDefault), Toast.LENGTH_LONG).show(); } }); alertDialogBuilder.setNegativeButton(context.getResources().getString(R.string.no), null); AlertDialog alertDialog = alertDialogBuilder.create(); return alertDialog; } AlertDialog getShareConfigAndLogDialogue(final Context context) { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setTitle(context.getResources().getString(R.string.shareConfigAndLogFilesWithDev)); alertDialogBuilder.setMessage(context.getResources().getString(R.string.shareConfigAndLogExplanation)); alertDialogBuilder.setPositiveButton(context.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { File dstZipFile = new File(Miscellaneous.getAnyContext().getCacheDir() + "/" + Settings.zipFileName); ArrayList srcFilesList = new ArrayList<>(); srcFilesList.add(Miscellaneous.getWriteableFolder() + "/" + XmlFileInterface.settingsFileName); srcFilesList.add(Miscellaneous.getWriteableFolder() + "/../shared_prefs/" + prefsFileName); String logFilePath = Miscellaneous.getWriteableFolder() + "/" + Miscellaneous.logFileName; if((new File(logFilePath)).exists()) srcFilesList.add(logFilePath); String logFilePathArchive = Miscellaneous.getWriteableFolder() + "/" + Miscellaneous.logFileName + "-old"; if((new File(logFilePathArchive)).exists()) srcFilesList.add(logFilePathArchive); String[] srcFiles = srcFilesList.toArray(new String[srcFilesList.size()]); if(dstZipFile.exists()) dstZipFile.delete(); Miscellaneous.zip(srcFiles, dstZipFile.getAbsolutePath()); /* Without root the zip file in the cache directory is not directly accessible. But have to route it through this content provider crap. */ String subject = "Automation logs"; Uri uri = Uri.parse("content://com.jens.automation2/" + Settings.zipFileName); Miscellaneous.sendEmail(ActivityMaintenance.this, "android-development@gmx.de", "Automation logs", getSystemInfo(), uri); } }); alertDialogBuilder.setNegativeButton(context.getResources().getString(R.string.no), null); AlertDialog alertDialog = alertDialogBuilder.create(); return alertDialog; } public static String getSystemInfo() { StringBuilder systemInfoText = new StringBuilder(); systemInfoText.append("Device details" + Miscellaneous.lineSeparator); systemInfoText.append("OS version: " + System.getProperty("os.version") + Miscellaneous.lineSeparator); systemInfoText.append("API Level: " + android.os.Build.VERSION.SDK + Miscellaneous.lineSeparator); systemInfoText.append("Device: " + android.os.Build.DEVICE + Miscellaneous.lineSeparator); systemInfoText.append("Model: " + android.os.Build.MODEL + Miscellaneous.lineSeparator); systemInfoText.append("Product: " + android.os.Build.PRODUCT + Miscellaneous.lineSeparator); systemInfoText.append("Flavor: " + BuildConfig.FLAVOR); return systemInfoText.toString(); } @Override protected void onResume() { super.onResume(); String folder = Miscellaneous.getWriteableFolder(); if (folder != null && folder.length() > 0) { tvFileStoreLocation.setText(String.format(getResources().getString(R.string.filesStoredAt), folder)); tvFileStoreLocation.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Uri selectedUri = Uri.parse(folder); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(selectedUri, "resource/folder"); if (intent.resolveActivityInfo(getPackageManager(), 0) != null) { startActivity(intent); } else { // if you reach this place, it means there is no any file // explorer app installed on your device Toast.makeText(ActivityMaintenance.this, getResources().getString(R.string.noFileManageInstalled), Toast.LENGTH_LONG).show(); } } }); } } }