// Copyright (C) 2012 LMIT Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.lmit.jenkins.android.configuration;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Environment;
import android.telephony.TelephonyManager;
import android.util.Base64;
import android.webkit.WebView;
import com.lmit.jenkins.android.activity.GenericListActivity;
import com.lmit.jenkins.android.activity.JenkinsMobi;
import com.lmit.jenkins.android.activity.R;
import com.lmit.jenkins.android.logger.Logger;
import com.lmit.jenkins.android.networking.DBAdapter;
import com.lmit.jenkins.android.networking.HudsonMobiSynchHttpClient;
import com.lmit.jenkins.android.networking.TwoPhaseAuthenticationRequiredException;
import com.lmit.jenkinscloud.commons.JenkinsCloudNode;
import com.lmit.jenkinscloud.commons.SyncCallback;
public class Configuration {
public static final Uri DEFAULT_HOME_NODE = new Uri.Builder().path("/qaexplorer/").build();
private static final String JENKINSCLOUD_USERNAME = "jenkinsmobi";
private static final String JENKINSCLOUD_PASSWORD = "jenkinsmobi";
// private static final String JENKINSCLOUD_URL = "http://dev.gitent-scm.com:9446/core";
private static final String JENKINSCLOUD_URL = "http://jenkinscloud.com:9446/core";
private static final String JENKINSCLOUD_SECRET = "WpoHdtbmnQwMdzhErIWtkZWsgF7tKyOIMcIGhn+n0FHo3Thp/9Dcgg";
/*BEGIN POST ERROR CONFIGURATION*/
public static final int POST_ERROR_TIME_OUT_CONNECT = 300;
public static final int POST_ERROR_TIME_OUT_RESPONSE = 300;
public static final String POST_ERROR_URL = "http://jenkinscloud.com/faults";
/*END POST ERROR CONFIGURATION*/
public static final float REQUIRED_API_LEVEL = 2.0f;
public static final String SUPPORT_MAIL_ADDRESS = "support@hudson-mobi.com";
public static final String CONFIGURATION_FILENAME = "hudsonmobile.conf";
public static final String KEY_SERVICE_HOSTNAME = "service.hostname";
public static final String KEY_USERNAME = "username";
public static final String KEY_PASSWORD = "password";
public static final String KEY_EULA_ACCEPTED = "eula.accepted";
public static final String KEY_AUDIT_ACCEPTED = "audit.accepted";
public static final String KEY_PRODUCT_NAME = "product.name";
public static final String KEY_PRODUCT_VERSION = "product.version";
public static final String KEY_API_VERSION = "api.version";
public static final String KEY_MSIS_DN = "msisdn";
public static final String KEY_SUBSCRIBER_ID = "subscriberid";
public static final String KEY_LAST_REFRESH_TS = "last.refresh.ts";
public static final String KEY_SCHEMA_VERSION = "schema.version";
public static final String KEY_CONNECTED = "connected";
public static final String KEY_JENKINS_URL = "jenkins.url";
public static final String KEY_JENKINS_USERNAME = "jenkins.username";
public static final String KEY_JENKINS_PASSWORD = "jenkins.password";
private static Configuration instance;
private Context appContext;
public String productVersion;
public String productName;
public String privateFolderPath;
public String targetCIProduct;
public String deviceLocale;
public String hudsonHostname;
private String username;
private String password;
public String msisdn;
public String subscriberId;
public String schema_version;
public boolean connected;
public boolean connectedChanged;
public String lastRefreshTimestamp;
public String userAgent;
public String jenkinsUrl;
public String jenkinsUsername;
public String jenkinsPassword;
public final static String[] ANDROID_RELEASE_CODENAMES = {"", "Base",
"Base 1.1", "Cupcake", "Donut", "Eclair", "Eclair", "Eclair", "Froyo",
"Gingerbread", "Gingerbread", "HoneyComb", "HoneyComb", "HoneyComb",
"Ice Cream Sandwich", "Ice Cream Sandwich"};
public static final String ACTIVITY_PACKAGE_NAME =
GenericListActivity.class.getPackage().getName();
public final String VALIDATION_SUCCEDED;
public final String OTP_TOKEN_REQUEST;
public String apkFileMD5;
private String homeNode = DEFAULT_HOME_NODE.getPath();
private String packageName;
public Context getAppContext() {
return appContext;
}
public String getUserAgent() {
return userAgent;
}
public static String getDeviceSoftwareVersion() {
return android.os.Build.VERSION.RELEASE;
}
// TO BE CALLED FIRST IN THE MAIN APP
public static Configuration getInstance(Context appContext) {
if (instance == null) {
instance = new Configuration(appContext);
instance.load();
}
return instance;
}
// WARN: DO NOT call this method prior than getInstance(Context)
public static Configuration getInstance() {
if (instance == null) {
throw new RuntimeException("Instance cannot be null now");
}
return instance;
}
private Configuration(Context context) {
this.appContext = context;
apkFileMD5 = computeApkChecksum(context);
VALIDATION_SUCCEDED = context
.getString(R.string.conf_save_validation_succeded);
OTP_TOKEN_REQUEST = context
.getString(R.string.conf_save_validation_otp_request);
packageName = context.getApplicationContext().getPackageName();
}
private String computeApkChecksum(Context context) {
ApplicationInfo appInfo = context.getApplicationInfo();
FileInputStream apkIn = null;
try {
apkIn = new FileInputStream(appInfo.sourceDir);
try {
byte[] apkBuff = new byte[6 * 1024];
MessageDigest md5 = MessageDigest.getInstance("MD5");
int apkRead = 0;
while (0 < (apkRead = apkIn.read(apkBuff))) {
md5.update(apkBuff, 0, apkRead);
}
byte[] apkDigest = md5.digest();
return toHex(apkDigest);
} catch (NoSuchAlgorithmException e) {
// This would never happen as MD5 is hardcoded
Logger.getInstance().error("Cannot compute APK MD5", e);
return "";
} finally {
apkIn.close();
}
} catch (IOException e1) {
// This would never happen as we are in the APK we're pointing to
Logger.getInstance().error("Cannot read my own APK", e1);
return "";
}
}
private String toHex(byte[] a) {
StringBuilder sb = new StringBuilder();
for (byte b : a)
sb.append(String.format("%02x", b & 0xff));
return sb.toString();
}
public String getTargetCIProduct() {
return targetCIProduct;
}
public String getDeviceLocale() {
return deviceLocale;
}
public String getPrivateFolderPath() {
return privateFolderPath;
}
public String getExternalStorageState() {
return Environment.getExternalStorageState();
}
public String getProductVersion() {
return productVersion;
}
public String getProductName() {
return productName;
}
public void load() {
SharedPreferences prefs =
appContext.getSharedPreferences(CONFIGURATION_FILENAME,
Context.MODE_PRIVATE);
deviceLocale =
appContext.getResources().getConfiguration().locale.getCountry()
.toLowerCase();
productName = appContext.getResources().getString(R.string.app_name);
productVersion = appContext.getResources().getString(R.string.app_version);
privateFolderPath = appContext.getFilesDir().getAbsolutePath();
if(Environment.getExternalStorageState()==Environment.MEDIA_MOUNTED){
privateFolderPath =
Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "Android" + File.separator + "data"
+ File.separator + "com.lmit.qaexplorer.android" + File.separator
+ "cache";
}
if(!new File(privateFolderPath).exists()){
boolean writeResult = new File(privateFolderPath).mkdirs();
Logger.getInstance().debug("Created data dirs: "+ writeResult);
}
username = prefs.getString(Configuration.KEY_USERNAME, JENKINSCLOUD_USERNAME);
password = prefs.getString(Configuration.KEY_PASSWORD, JENKINSCLOUD_PASSWORD);
// hudsonHostname = prefs.getString(Configuration.KEY_SERVICE_HOSTNAME, JENKINSCLOUD_URL);
hudsonHostname = JENKINSCLOUD_URL;
msisdn = prefs.getString(Configuration.KEY_MSIS_DN, "no-msisdn-supplied");
subscriberId = prefs.getString(Configuration.KEY_SUBSCRIBER_ID, null);
lastRefreshTimestamp =
prefs.getString(Configuration.KEY_LAST_REFRESH_TS, "0");
schema_version =
prefs.getString(Configuration.KEY_SCHEMA_VERSION, "" + DBAdapter.DATABASE_VERSION);
connected = prefs.getBoolean(Configuration.KEY_CONNECTED, true);
connectedChanged = false;
userAgent =
new WebView(appContext).getSettings().getUserAgentString() + " APK-MD5/" + apkFileMD5 + " Package/" + packageName;
jenkinsUrl = prefs.getString(Configuration.KEY_JENKINS_URL, "http://hudson-mobi.com/hudson");
jenkinsUsername = prefs.getString(Configuration.KEY_JENKINS_USERNAME, "guest");
jenkinsPassword = prefs.getString(Configuration.KEY_JENKINS_PASSWORD, "guest");
Logger.getInstance().info(
String.format("PackageName:%s APK:%s",
packageName,
apkFileMD5));
}
public void save() {
SharedPreferences prefs =
appContext.getSharedPreferences(CONFIGURATION_FILENAME,
Context.MODE_PRIVATE);
Editor prefEditor = prefs.edit();
prefEditor.putString(Configuration.KEY_SERVICE_HOSTNAME, hudsonHostname);
prefEditor.putString(Configuration.KEY_USERNAME, username);
prefEditor.putString(Configuration.KEY_PASSWORD, password);
prefEditor.putString(Configuration.KEY_MSIS_DN, msisdn);
prefEditor.putString(Configuration.KEY_SUBSCRIBER_ID, subscriberId);
prefEditor.putString(Configuration.KEY_SCHEMA_VERSION, schema_version);
prefEditor.putString(Configuration.KEY_LAST_REFRESH_TS,
lastRefreshTimestamp);
prefEditor.putString(Configuration.KEY_JENKINS_URL, jenkinsUrl);
prefEditor.putString(Configuration.KEY_JENKINS_USERNAME, jenkinsUsername);
prefEditor.putString(Configuration.KEY_JENKINS_PASSWORD, jenkinsPassword);
if (connectedChanged) {
prefEditor.putBoolean(Configuration.KEY_CONNECTED, connected);
connectedChanged = false;
}
prefEditor.commit();
}
public static void resetIstance() {
instance = null;
}
public String getHudsonHostname() {
return hudsonHostname;
}
public String getUsername() {
return JENKINSCLOUD_SECRET;
}
public String getPassword() {
return subscriberId;
}
public void setHudsonHostname(String host) {
hudsonHostname = host;
}
@Override
public boolean equals(Object o) {
boolean result = false;
if (o != null && o instanceof Configuration) {
Configuration __o = (Configuration) o;
if (__o.getHudsonHostname() != null && getHudsonHostname() != null) { // check
// hostname
result = __o.getHudsonHostname().equals(getHudsonHostname());
} else {
result = __o.getHudsonHostname() == null && getHudsonHostname() == null;
}
if (result) {
if (__o.getUsername() != null && getUsername() != null) { // check
// username
result = __o.getUsername().equals(getUsername());
} else {
result = __o.getUsername() == null && getUsername() == null;
}
if (result) { // if usernames match check passwords
if (__o.getPassword() != null && getPassword() != null) {
result = __o.getPassword().equals(getPassword());
} else {
result = __o.getPassword() == null && getPassword() == null;
}
}
}
}
return result;
}
public void setConnected(boolean connected, boolean saveConfig) {
this.connected = connected;
this.connectedChanged = saveConfig;
}
public boolean isConnected() {
return this.connected;
}
public String getMsisdn() {
return msisdn;
}
public void setMsisdn(String msisdn) {
this.msisdn = msisdn;
}
public String getLastRefreshTimestamp() {
return lastRefreshTimestamp;
}
public void setLastRefreshTimestamp(String lastRefreshTimestamp) {
this.lastRefreshTimestamp = lastRefreshTimestamp;
}
public String getHudsonHostnameOnlyBase() {
// http://xxxxx:xxx/core
String host = getHudsonHostname();
int i = host.lastIndexOf('/');
return host.substring(0, i);
}
public String getSchema_version() {
return schema_version;
}
public void setSchema_version(String schema_version) {
this.schema_version = schema_version;
}
public String getSubscriberId() {
return subscriberId;
}
public void setSubscriberID(String subscriberId) {
this.subscriberId = subscriberId;
}
public HashMap<String, String> getRequestHeaders() {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("x-jenkinscloud-msisdn", msisdn);
headers.put("x-jenkinscloud-subscriberid", subscriberId);
return headers;
}
public String validate(String otp) throws TwoPhaseAuthenticationRequiredException {
HudsonMobiSynchHttpClient client = new HudsonMobiSynchHttpClient();
client.setUserHeaders(getRequestHeaders());
final StringBuilder validationResult = new StringBuilder();
JenkinsCloudPlugin jenkinsPlugin = getJenkinsPlugin();
StatusLine statusLine;
HttpResponse response = null;
try {
if (otp != null) {
Map<String, String> headers = client.getUserHeaders();
headers.put(HudsonMobiSynchHttpClient.X_AUTH_OTP_HEADER, otp);
}
response = client.callPost(jenkinsPlugin.getData(),
buildUrl(hudsonHostname, "../registry/rpc/config"));
statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
switch (statusCode) {
case HttpURLConnection.HTTP_OK:
validationResult.append(VALIDATION_SUCCEDED);
break;
case HttpURLConnection.HTTP_PRECON_FAILED:
throw new TwoPhaseAuthenticationRequiredException(response);
default:
validationResult.append(statusLine.getReasonPhrase());
break;
}
return validationResult.toString();
} catch (Exception e) {
if(e instanceof TwoPhaseAuthenticationRequiredException) {
throw (TwoPhaseAuthenticationRequiredException) e;
}
return e.getLocalizedMessage();
} finally {
if(response != null) {
try {
response.getEntity().consumeContent();
} catch (IOException e) {
}
}
}
}
private JenkinsCloudPlugin getJenkinsPlugin() {
JenkinsCloudPlugin plugin = new JenkinsCloudPlugin("JenkinsCI");
plugin.description = "JenkinsCI";
plugin.type = "JenkinsCI";
plugin.url = jenkinsUrl;
plugin.username = jenkinsUsername;
plugin.password = jenkinsPassword;
return plugin;
}
private String buildUrl(String baseUrl, String path) {
StringBuilder outPath = new StringBuilder(baseUrl);
if(!baseUrl.endsWith("/")) {
outPath.append("/");
}
while(path.startsWith("/")) {
path = path.substring(1);
}
outPath.append(path);
return outPath.toString();
}
public String getHomeNode() {
return this.homeNode;
}
public void setHomeNode(Uri intentUri) {
this.homeNode = intentUri.getPath();
String apkMd5 = intentUri.getQueryParameter("APK-MD5");
if(apkMd5 != null) {
apkFileMD5 = apkMd5;
} else {
computeApkChecksum(appContext);
}
String packageName = intentUri.getQueryParameter("PackageName");
if(packageName != null) {
this.packageName = packageName;
} else {
this.packageName = appContext.getPackageName();
}
load();
}
public boolean detectSubscriberId() {
Logger log = Logger.getInstance();
boolean simChange = false;
TelephonyManager telephonyManager =
(TelephonyManager) this.appContext
.getSystemService(Context.TELEPHONY_SERVICE);
String subscriberId = telephonyManager.getSubscriberId();
String deviceId = telephonyManager.getDeviceId();
if(deviceId == null || deviceId.length() <= 0) {
WifiManager wifiManager = (WifiManager) this.appContext
.getSystemService(Context.WIFI_SERVICE);
WifiInfo connInfo = wifiManager.getConnectionInfo();
if(connInfo != null) {
deviceId = connInfo.getMacAddress();
}
}
if(subscriberId == null) {
subscriberId = "";
}
UUID deviceUuid = UUID.nameUUIDFromBytes((deviceId + subscriberId).getBytes());
if (subscriberId == null || subscriberId.trim().length() <= 0) {
log.debug("No subscriber-id detected (possibly phone without SIM or in flight-mode)");
String storedSubscriberId = Configuration.getInstance().getSubscriberId();
if (storedSubscriberId == null) {
log.debug("Generating a new subscriber UUID");
subscriberId = deviceUuid.toString();
} else {
subscriberId = storedSubscriberId;
}
} else {
subscriberId = deviceUuid.toString();
}
String storedSubscriberId = Configuration.getInstance().getSubscriberId();
if (!subscriberId.equals(storedSubscriberId)) {
simChange = true;
log.debug("SubscriberID=" + subscriberId);
Configuration.getInstance().setSubscriberID(subscriberId);
Configuration.getInstance().save();
}
return simChange;
}
}