/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Curt Binder
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package info.curtbinder.reefangel.phone;
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import info.curtbinder.reefangel.service.MessageCommands;
import info.curtbinder.reefangel.service.UpdateService;
import info.curtbinder.reefangel.service.XMLReadException;
public class RAApplication extends Application {
private static final String TAG = RAApplication.class.getSimpleName();
private static final String NUMBER_PATTERN = "\\d+";
private static final String HOST_PATTERN =
"^(?i:[[0-9][a-z]]+)(?i:[\\w\\.\\-]*)(?i:[[0-9][a-z]]+)$";
private static final String USERID_PATTERN = "[\\w\\-\\.\\x20]+";
@SuppressWarnings("unused")
private static final String WIFI_LOOKUP =
"http://forum.reefangel.com/getwifi.php?id=%1$s&pwd=%2$s";
// Preferences
public RAPreferences raprefs;
// Error code stuff
private String[] errorCodes;
private String[] errorCodesStrings;
private String errorCodeMessage;
public int errorCode;
public int errorCount;
public static String getFancyDate(long when) {
DateFormat fmt =
DateFormat.getDateTimeInstance(DateFormat.SHORT,
DateFormat.SHORT,
Locale.getDefault());
return fmt.format(new Date(when));
}
public void onCreate() {
errorCodes = getResources().getStringArray(R.array.errorCodes);
errorCodesStrings = getResources().getStringArray(R.array.errorCodesStrings);
errorCodeMessage = ""; // set to no error message
errorCode = 0; // set to no error initially
raprefs = new RAPreferences(this);
// initialize the error count
errorCount = 0;
}
public void onTerminate() {
super.onTerminate();
}
public void restartAutoUpdateService() {
cancelAutoUpdateService();
startAutoUpdateService();
}
public void cancelAutoUpdateService() {
PendingIntent pi = getUpdateIntent();
AlarmManager am =
(AlarmManager) getSystemService(Context.ALARM_SERVICE);
// cancel the repeating service
am.cancel(pi);
}
public void startAutoUpdateService() {
// check to see if we need to start the repeating update service
// grab the service interval, make sure it's greater than 0
long interval = raprefs.getUpdateInterval();
if (interval == 0) {
Log.d(TAG, "disabled autoupdate");
return;
}
int up = raprefs.getUpdateProfile();
if (raprefs.isCommunicateController()) {
int p = getSelectedProfile();
Log.d(TAG, "UP: " + up + " P: " + p);
if (isAwayProfileEnabled()) {
if ((up == Globals.profileOnlyAway)
&& (p != Globals.profileAway)) {
// only run on away profile and we are not on away profile
Log.d(TAG, "only run on away, not away");
return;
} else if ((up == Globals.profileOnlyHome)
&& (p != Globals.profileHome)) {
// only run on home profile and we are not on home profile
Log.d(TAG, "only run on home, not home");
return;
}
}
}
// create a status query message
PendingIntent pi = getUpdateIntent();
// setup alarm service to wake up and start the service periodically
AlarmManager am =
(AlarmManager) getSystemService(Context.ALARM_SERVICE);
am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(),
interval, pi);
}
private PendingIntent getUpdateIntent() {
Intent i = new Intent(this, UpdateService.class);
i.setAction(MessageCommands.QUERY_STATUS_INTENT);
i.putExtra(MessageCommands.AUTO_UPDATE_PROFILE_INT,
raprefs.getUpdateProfile());
PendingIntent pi =
PendingIntent.getService(this, -1, i,
PendingIntent.FLAG_CANCEL_CURRENT);
return pi;
}
// Error Logging
public void clearErrorCode() {
errorCode = 0;
errorCodeMessage = "";
}
private String getSimpleErrorMessage ( String msg ) {
String s = "";
if ( msg.contains( "EHOSTUNREACH" ) ) {
s = "Host unreachable: " + raprefs.getHost() + ":" + raprefs.getPort();
} else if ( msg.contains( "ECONNREFUSED" ) ) {
s = "Connection Refused: " + raprefs.getHost() + ":" + raprefs.getPort();
} else if ( msg.contains( "ECONNRESET" ) ) {
s = "Connection Reset by Peer";
} else {
s = msg;
}
return s;
}
public void error(int errorCodeIndex, Throwable t, String msg) {
errorCode = Integer.parseInt(errorCodes[errorCodeIndex]);
if ( t.getMessage() != null )
errorCodeMessage = getSimpleErrorMessage(t.getMessage());
if ( errorCode == 15 )
// timeout error
errorCodeMessage = String.format( Locale.getDefault(),
getString(R.string.messageErrorTimeout),
raprefs.getHost(), raprefs.getPort());
Log.d(TAG, "Error: " + errorCode + ", " + errorCodeMessage);
// if logging enabled, save the log
if (raprefs.isLoggingEnabled()) {
if (!hasExternalStorage()) {
// doesn't have external storage
Toast.makeText(this,
getString(R.string.messageNoExternalStorage),
Toast.LENGTH_LONG).show();
return;
}
boolean keepFile = raprefs.isLoggingAppendFile();
try {
String sFile = getLoggingFile();
FileWriter fw = new FileWriter(sFile, keepFile);
PrintWriter pw = new PrintWriter(fw);
DateFormat dft =
DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
DateFormat.DEFAULT,
Locale.getDefault());
pw.println(dft.format(Calendar.getInstance().getTime()));
String s =
String.format("Profile: %s\nHost: %s:%s\nUser ID: %s",
(getSelectedProfile() == 1) ? "Away"
: "Home",
raprefs.getHost(), raprefs.getPort(),
raprefs.getUserId()
);
pw.println(s);
pw.println(msg);
if ( t instanceof XMLReadException) {
// we have an XML read exception, get the xml data if any
pw.println( ((XMLReadException) t).getXmlData() );
}
pw.println(t.toString());
pw.println("Stack Trace:");
pw.flush();
t.printStackTrace(pw);
pw.println("----");
pw.flush();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String getErrorMessage() {
String s = getString(R.string.messageUnknownError);
// loop through array of error codes and match with the current code
for (int i = 0; i < errorCodes.length; i++) {
if (Integer.parseInt(errorCodes[i]) == errorCode) {
// found code
s = String.format(Locale.US, "%s %d: %s", getString(R.string.messageError),
errorCode,
(errorCodeMessage == "" ) ? errorCodesStrings[i]
: errorCodeMessage);
break;
}
}
return s;
}
public boolean canErrorRetry() {
boolean f = false;
if (errorCount <= raprefs.getNotificationErrorRetryMax()) {
f = true;
}
return f;
}
public void clearErrorRetryCount() {
errorCount = Globals.errorRetryNone;
}
public void increaseErrorCount() {
errorCount++;
}
public String getLoggingDirectory() {
String s =
"" + Environment.getExternalStorageDirectory()
+ Environment.getDataDirectory() + "/"
+ Globals.PACKAGE + "/";
return s;
}
public String getLoggingFile() {
return getLoggingDirectory() + Globals.loggingFile;
}
public boolean isLoggingFilePresent() {
boolean f = false;
File l = new File(getLoggingFile());
if ((l != null) && (l.exists()))
f = true;
return f;
}
public void deleteLoggingFile() {
File l = new File(getLoggingFile());
if (l != null && l.exists())
l.delete();
}
public boolean hasExternalStorage() {
boolean f = false;
File path = new File(getLoggingDirectory());
path.mkdirs();
File file = new File(path, "test.txt");
file.mkdirs();
if (file != null) {
if (file.exists()) {
f = true;
file.delete();
}
}
return f;
}
private boolean isNumber(Object value) {
if ((!value.toString().equals(""))
&& (value.toString().matches(NUMBER_PATTERN))) {
return true;
}
return false;
}
public boolean validateHost(Object host) {
// host validation here
String h = host.toString();
// Hosts must:
// - not start with 'http://'
// - only contain: alpha, number, _, -, .
// - end with: alpha or number
if (!h.matches(HOST_PATTERN)) {
// invalid host
Toast.makeText(this,
this.getString(R.string.prefHostInvalidHost)
+ ": " + host.toString(),
Toast.LENGTH_SHORT
).show();
return false;
}
return true;
}
public boolean validatePort(Object port) {
if (!isNumber(port)) {
// not a number
Toast.makeText(this,
getString(R.string.messageNotNumber) + ": "
+ port.toString(), Toast.LENGTH_SHORT
)
.show();
return false;
} else {
// it's a number, verify it's within range
// TODO: convert min & max ports to int value defines
int min = Integer.parseInt(getString(R.string.prefPortMin));
int max = Integer.parseInt(getString(R.string.prefPortMax));
int v = Integer.parseInt((String) port.toString());
// check if it's less than the min value or if it's greater than
// the max value
if ((v < min) || (v > max)) {
Toast.makeText(this,
getString(R.string.prefPortInvalidPort)
+ ": " + port.toString(),
Toast.LENGTH_SHORT
).show();
return false;
}
}
return true;
}
public boolean validateUser(Object user) {
String u = user.toString();
if (!u.matches(USERID_PATTERN)) {
// invalid userid
Toast.makeText(this,
getString(R.string.prefUserIdInvalid) + ": "
+ user.toString(), Toast.LENGTH_SHORT
)
.show();
return false;
}
return true;
}
public String getPWMOverrideChannelName(int channel) {
String name = "";
switch ( channel ) {
default:
name = getString(R.string.labelChannel);
break;
case Globals.OVERRIDE_DAYLIGHT:
name = raprefs.getControllerLabel(Globals.DP_INDEX);
break;
case Globals.OVERRIDE_ACTINIC:
name = raprefs.getControllerLabel(Globals.AP_INDEX);
break;
case Globals.OVERRIDE_CHANNEL0:
case Globals.OVERRIDE_CHANNEL1:
case Globals.OVERRIDE_CHANNEL2:
case Globals.OVERRIDE_CHANNEL3:
case Globals.OVERRIDE_CHANNEL4:
case Globals.OVERRIDE_CHANNEL5:
name = raprefs.getDimmingModuleChannelLabel(channel - Globals.OVERRIDE_CHANNEL0);
break;
case Globals.OVERRIDE_AI_WHITE:
name = getString( R.string.labelAI ) + " " + getString( R.string.labelWhite );
break;
case Globals.OVERRIDE_AI_ROYALBLUE:
name = getString( R.string.labelAI ) + " " + getString( R.string.labelRoyalBlue );
break;
case Globals.OVERRIDE_AI_BLUE:
name = getString( R.string.labelAI ) + " " + getString( R.string.labelBlue );
break;
case Globals.OVERRIDE_RF_WHITE:
name = getString( R.string.labelRadion ) + " " + getString( R.string.labelWhite );
break;
case Globals.OVERRIDE_RF_ROYALBLUE:
name = getString( R.string.labelRadion ) + " " + getString( R.string.labelRoyalBlue );
break;
case Globals.OVERRIDE_RF_RED:
name = getString( R.string.labelRadion ) + " " + getString( R.string.labelRed );
break;
case Globals.OVERRIDE_RF_GREEN:
name = getString( R.string.labelRadion ) + " " + getString( R.string.labelGreen );
break;
case Globals.OVERRIDE_RF_BLUE:
name = getString( R.string.labelRadion ) + " " + getString( R.string.labelBlue );
break;
case Globals.OVERRIDE_RF_INTENSITY:
name = getString( R.string.labelRadion ) + " " + getString( R.string.labelIntensity );
break;
case Globals.OVERRIDE_16CH_CHANNEL0:
case Globals.OVERRIDE_16CH_CHANNEL1:
case Globals.OVERRIDE_16CH_CHANNEL2:
case Globals.OVERRIDE_16CH_CHANNEL3:
case Globals.OVERRIDE_16CH_CHANNEL4:
case Globals.OVERRIDE_16CH_CHANNEL5:
case Globals.OVERRIDE_16CH_CHANNEL6:
case Globals.OVERRIDE_16CH_CHANNEL7:
case Globals.OVERRIDE_16CH_CHANNEL8:
case Globals.OVERRIDE_16CH_CHANNEL9:
case Globals.OVERRIDE_16CH_CHANNEL10:
case Globals.OVERRIDE_16CH_CHANNEL11:
case Globals.OVERRIDE_16CH_CHANNEL12:
case Globals.OVERRIDE_16CH_CHANNEL13:
case Globals.OVERRIDE_16CH_CHANNEL14:
case Globals.OVERRIDE_16CH_CHANNEL15:
name = raprefs.getSCDimmingModuleChannelLabel( channel - Globals.OVERRIDE_16CH_CHANNEL0 );
break;
}
return name;
}
// TODO cleanup this function, remove repeated format calls, simplify
public String getPWMOverrideMessageDisplay ( int channel ) {
String msg = "";
String name = getPWMOverrideChannelName(channel);
switch ( channel ) {
case Globals.OVERRIDE_DAYLIGHT:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefDPVisibilityTitle) );
break;
case Globals.OVERRIDE_ACTINIC:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefAPVisibilityTitle) );
break;
case Globals.OVERRIDE_CHANNEL0:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpDimmingCh0LabelTitle));
break;
case Globals.OVERRIDE_CHANNEL1:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpDimmingCh1LabelTitle) );
break;
case Globals.OVERRIDE_CHANNEL2:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpDimmingCh2LabelTitle) );
break;
case Globals.OVERRIDE_CHANNEL3:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpDimmingCh3LabelTitle) );
break;
case Globals.OVERRIDE_CHANNEL4:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpDimmingCh4LabelTitle) );
break;
case Globals.OVERRIDE_CHANNEL5:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpDimmingCh5LabelTitle) );
break;
case Globals.OVERRIDE_AI_WHITE:
case Globals.OVERRIDE_AI_ROYALBLUE:
case Globals.OVERRIDE_AI_BLUE:
case Globals.OVERRIDE_RF_WHITE:
case Globals.OVERRIDE_RF_ROYALBLUE:
case Globals.OVERRIDE_RF_RED:
case Globals.OVERRIDE_RF_GREEN:
case Globals.OVERRIDE_RF_BLUE:
case Globals.OVERRIDE_RF_INTENSITY:
msg = name + " " + getString( R.string.labelChannel );
break;
case Globals.OVERRIDE_16CH_CHANNEL0:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh0LabelTitle));
break;
case Globals.OVERRIDE_16CH_CHANNEL1:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh1LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL2:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh2LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL3:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh3LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL4:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh4LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL5:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh5LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL6:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh6LabelTitle));
break;
case Globals.OVERRIDE_16CH_CHANNEL7:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh7LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL8:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh8LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL9:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh9LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL10:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh10LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL11:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh11LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL12:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh12LabelTitle));
break;
case Globals.OVERRIDE_16CH_CHANNEL13:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh13LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL14:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh14LabelTitle) );
break;
case Globals.OVERRIDE_16CH_CHANNEL15:
msg = String.format( Locale.getDefault(),
getString( R.string.messagePWMPopupCustom),
name, getString(R.string.prefExpSCDimmingCh15LabelTitle) );
break;
}
return msg;
}
// Preferences
public boolean isFirstRun() {
// First run will be determined by:
// if the first run key is NOT set AND
// if the host key is NOT set OR if it's the same as the default
boolean fFirst = raprefs.isFirstRun();
// if it's already set, no need to compare the hosts
if (!fFirst) {
Log.w( TAG, "First run already set" );
return false;
}
// if it's not set (as in existing installations), check the host
// the host should be set and it should not be the same as the default
if (!raprefs.isMainHostSet())
return true;
// if we have made it here, then it's an existing install where the user
// has the host set to something other than the default
// so we will go ahead and clear the first run prompt for them
raprefs.disableFirstRun();
return false;
}
public void displayChangeLog(ActionBarActivity a) {
// check version code stored in preferences vs the version stored in
// running code
// display the changelog if the values are different
int previous = raprefs.getPreviousCodeVersion();
int current = 0;
try {
current = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
} catch (NameNotFoundException e) {
// safely ignore the error
}
if (current > previous) {
// save code version in preferences
raprefs.setPreviousCodeVersion(current);
// newer version, display changelog
DialogSupportChangelog dlg = new DialogSupportChangelog();
dlg.show(a.getSupportFragmentManager(), "dlg");
}
// deletePref( R.string.prefPreviousCodeVersion );
}
// Profiles
public int getSelectedProfile() {
return raprefs.getSelectedProfile();
}
public void setSelectedProfile(int profile) {
if (profile > Globals.profileAway)
return;
raprefs.setSelectedProfile(profile);
restartAutoUpdateService();
}
public boolean isAwayProfileEnabled() {
// String host = raprefs.getAwayHost();
// Log.d( TAG, "isAwayProfileEnabled: " + host );
// get away host, compare to empty host
// if host is set, then the profile is enabled
// if port is not set, that implies default port
return raprefs.isAwayHostSet();
}
}