package net.osmand.plus.osmo;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Build;
import android.provider.Settings.Secure;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.notifications.OsmandNotification;
import net.osmand.plus.notifications.OsmandNotification.NotificationType;
import org.apache.commons.logging.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
public class OsMoService implements OsMoReactor {
private static final String HTTP_API_PREPARE = "http://api.osmo.mobi/prepare";
private static final String HTTPS_API_PREPARE = "https://api.osmo.mobi/prepare";
private static final String HTTP_AUTH = "http://api.osmo.mobi/auth";
private static final String HTTPS_AUTH = "https://api.osmo.mobi/auth";
public static final String REGENERATE_CMD = "TRACKER_REGENERATE_ID";
public static final String SIGN_IN_URL = "http://osmo.mobi/signin?key=";
private OsMoThread thread;
private List<OsMoReactor> listReactors = new java.util.concurrent.CopyOnWriteArrayList<OsMoReactor>();
private ConcurrentLinkedQueue<String> commands = new ConcurrentLinkedQueue<String>();
private OsmandApplication app;
private static final Log log = PlatformUtil.getLog(OsMoService.class);
public static final String SHARE_TRACKER_URL = "http://z.osmo.mobi/connect?id=";
public static final String SHARE_GROUP_URL = "http://z.osmo.mobi/join?id=";
public static final String SIGNED_IN_CONTAINS = "z.osmo.mobi/login";
public static final String TRACK_URL = "http://osmo.mobi/s/";
private String lastRegistrationError = null;
private OsMoPlugin plugin;
private boolean enabled = false;
private BroadcastReceiver broadcastReceiver;
private Notification notification;
public final static String OSMO_REGISTER_AGAIN = "OSMO_REGISTER_AGAIN"; //$NON-NLS-1$
private final static int SIMPLE_NOTFICATION_ID = 5;
private class HttpPostWriter {
BufferedWriter writer;
boolean first;
HttpPostWriter(OutputStream outputStream) {
this.writer = new BufferedWriter(new OutputStreamWriter(outputStream));
this.first = true;
}
void addPair(String key, String value) throws IOException {
if (this.first)
this.first = false;
else
this.writer.write("&");
this.writer.write(URLEncoder.encode(key, "UTF-8"));
this.writer.write("=");
this.writer.write(URLEncoder.encode(value, "UTF-8"));
}
void flush() throws IOException {
this.writer.flush();
this.writer.close();
}
}
public OsMoService(final OsmandApplication app, OsMoPlugin plugin) {
this.app = app;
this.plugin = plugin;
listReactors.add(this);
listReactors.add(plugin);
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
notification = null;
NotificationManager mNotificationManager = (NotificationManager) app
.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.cancel(SIMPLE_NOTFICATION_ID);
registerAsync();
}
};
app.registerReceiver(broadcastReceiver, new IntentFilter(OSMO_REGISTER_AGAIN));
}
private void registerAsync() {
new AsyncTask<Void, Void, Void>() {
public Void doInBackground(Void... voids ) {
try {
registerOsmoDeviceKey();
onConnected();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}.execute((Void)null);
}
public boolean isConnected() {
return thread != null && thread.isConnected();
}
public boolean isActive() {
return thread != null && thread.isActive();
}
public long getLastCommandTime() {
if(isConnected()) {
return thread.getLastCommandTime();
}
return 0;
}
public List<String> getHistoryOfCommands() {
if(thread == null) {
return Collections.emptyList();
}
return new ArrayList<String>(thread.getLastCommands());
}
public long getConnectionTime() {
return thread == null || !thread.isConnected() ? System.currentTimeMillis() : thread.getConnectionTime();
}
protected List<OsMoReactor> getListReactors() {
return listReactors;
}
public String getLastRegistrationError() {
return lastRegistrationError;
}
public boolean connect(boolean forceReconnect) {
if(thread != null) {
if(!forceReconnect ) {
return isConnected();
}
thread.stopConnection();
}
thread = new OsMoThread(this);
enabled = true;
app.getNotificationHelper().refreshNotification(NotificationType.OSMO);
return true;
}
public boolean isEnabled() {
return enabled;
}
public void disconnect() {
if(thread != null) {
enabled = false;
thread.stopConnection();
app.getSettings().OSMO_LAST_PING.set(0l);
app.getNotificationHelper().refreshNotification(NotificationType.OSMO);
}
}
public void registerReactor(OsMoReactor reactor) {
if(!listReactors.contains(reactor)) {
ArrayList<OsMoReactor> lst = new ArrayList<OsMoReactor>(listReactors);
lst.add(reactor);
listReactors = lst;
}
}
public void removeReactor(OsMoReactor s) {
if(listReactors.contains(s)) {
ArrayList<OsMoReactor> lst = new ArrayList<OsMoReactor>(listReactors);
lst.remove(s);
listReactors = lst;
}
}
public String registerOsmoDeviceKey() throws IOException {
URL url = new URL(plugin.useHttps()? HTTPS_AUTH : HTTP_AUTH);
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
// Add your data
HttpPostWriter postWriter = new HttpPostWriter(conn.getOutputStream());
postWriter.addPair("android_id", Secure.getString(app.getContentResolver(),
Secure.ANDROID_ID));
postWriter.addPair("android_model", Build.MODEL);
postWriter.addPair("imei", "0");
postWriter.addPair("android_product", Build.PRODUCT);
postWriter.addPair("client", Version.getFullVersion(app));
postWriter.addPair("osmand", Version.getFullVersion(app));
// Execute HTTP Post Request
postWriter.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String r = reader.readLine();
reader.close();
conn.disconnect();
log.info("Authorization key : " + r);
final JSONObject obj = new JSONObject(r);
if(obj.has("error")) {
lastRegistrationError = obj.getString("error");
throw new OsMoConnectionException(obj.getString("error"));
}
app.getSettings().OSMO_DEVICE_KEY.set(obj.getString("key"));
return obj.getString("key");
} catch (JSONException e) {
throw new IOException(e);
}
}
public static class SessionInfo {
public String hostName;
public String port;
public String token;
public String uid;
public String username;
// after auth
public String protocol = "";
public String groupTrackerId;
public String trackerId;
public long serverTimeDelta;
public long motdTimestamp;
public String motd = "";
}
public SessionInfo getCurrentSessionInfo() {
if(thread == null) {
return null;
}
return thread.getSessionInfo();
}
public String getRegisteredUserName() {
SessionInfo si = getCurrentSessionInfo();
if(si != null && si.username != null && si.username.length() > 0) {
return si.username;
}
return null;
}
public String getMyGroupTrackerId() {
String myGroupTrackerId = "";
SessionInfo currentSessionInfo = getCurrentSessionInfo();
if (currentSessionInfo != null) {
myGroupTrackerId = currentSessionInfo.groupTrackerId;
}
return myGroupTrackerId;
}
public String getMyTrackerId() {
String myGroupTrackerId = "";
SessionInfo currentSessionInfo = getCurrentSessionInfo();
if (currentSessionInfo != null) {
myGroupTrackerId = currentSessionInfo.trackerId;
}
return myGroupTrackerId;
}
public SessionInfo prepareSessionToken() throws IOException {
String deviceKey = app.getSettings().OSMO_DEVICE_KEY.get();
if (deviceKey.length() == 0) {
deviceKey = registerOsmoDeviceKey();
}
URL url = new URL(plugin.useHttps() ? HTTPS_API_PREPARE : HTTP_API_PREPARE);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
try {
conn.setDoOutput(true);
HttpPostWriter postWriter = new HttpPostWriter(conn.getOutputStream());
// Add your data
postWriter.addPair("app", Version.getFullVersion(app));
postWriter.addPair("key", deviceKey);
if (app.getSettings().OSMO_USER_PWD.get() != null) {
postWriter.addPair("auth", app.getSettings().OSMO_USER_PWD.get());
}
postWriter.addPair("protocol", "1");
// Execute HTTP Post Request
postWriter.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String r = reader.readLine();
reader.close();
conn.disconnect();
log.info("Authorization key : " + r);
final JSONObject obj = new JSONObject(r);
if (obj.has("error")) {
lastRegistrationError = obj.getString("error");
runNotification(lastRegistrationError);
return null;
}
if (!obj.has("address")) {
lastRegistrationError = "Host name not specified";
throw new RuntimeException("Host name not specified");
}
if (!obj.has("token")) {
lastRegistrationError = "Token not specified by server";
throw new RuntimeException("Token not specified by server");
}
SessionInfo si = new SessionInfo();
String a = obj.getString("address");
if (obj.has("name")) {
si.username = obj.getString("name");
}
if (obj.has("uid")) {
si.uid = obj.getString("uid");
}
int i = a.indexOf(':');
si.hostName = a.substring(0, i);
si.port = a.substring(i + 1);
si.token = obj.getString("token");
return si;
} catch (IOException e) {
throw e;
} catch (JSONException e) {
throw new IOException(e);
}
}
private void runNotification(final String error) {
final Activity ga = plugin.getGroupsActivity();
if(ga != null) {
app.runInUIThread(new Runnable() {
@Override
public void run() {
showRegisterAgain(ga, app.getString(R.string.osmo_auth_error, error));
}
});
} else if (notification == null) {
Intent notificationIntent = new Intent(OSMO_REGISTER_AGAIN);
PendingIntent intent = PendingIntent.getBroadcast(app, 0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
android.support.v4.app.NotificationCompat.Builder bld = new NotificationCompat.Builder(app);
bld.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
bld.setContentInfo(app.getString(R.string.osmo_auth_error, error));
bld.setContentIntent(intent);
bld.setContentTitle(app.getString(R.string.osmo_auth_error_short));
bld.setSmallIcon(R.drawable.bgs_icon);
NotificationManager mNotificationManager = (NotificationManager) app
.getSystemService(Context.NOTIFICATION_SERVICE);
notification = bld.getNotification();
mNotificationManager.notify(SIMPLE_NOTFICATION_ID, notification);
}
}
protected void showRegisterAgain(Activity ga, String msg) {
AlertDialog.Builder bld = new AlertDialog.Builder(ga);
bld.setMessage(msg);
bld.setPositiveButton(R.string.shared_string_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
registerAsync();
}
});
bld.setNegativeButton(R.string.shared_string_cancel, null);
}
public void showErrorMessage(String string) {
app.showToastMessage(app.getString(R.string.osmo_io_error) + string);
}
public void pushCommand(String cmd) {
commands.add(cmd);
}
@Override
public String nextSendCommand(OsMoThread tracker) {
if(System.currentTimeMillis() - app.getSettings().OSMO_LAST_PING.get() > 30000) {
app.getSettings().OSMO_LAST_PING.set(System.currentTimeMillis());
}
if(!commands.isEmpty()) {
return commands.poll();
}
return null;
}
@Override
public boolean acceptCommand(String command, String id, String data, JSONObject obj, OsMoThread tread) {
if(command.equals("MOTD")) {
SessionInfo si = getCurrentSessionInfo();
if(si != null) {
if(data.startsWith("[")){
try {
data = new JSONArray(data).getString(0);
} catch (JSONException e) {
e.printStackTrace();
}
}
si.motd = data;
}
return true;
} else if(command.equals("TRACK_GET")) {
try {
JSONArray ar = new JSONArray(data);
AsyncTask<JSONObject, String, String> task = plugin.getDownloadGpxTask(false);
JSONObject[] a = new JSONObject[ar.length()];
for(int i = 0; i < a.length; i++) {
a[i] = (JSONObject) ar.get(i);
}
task.execute(a);
} catch (JSONException e) {
e.printStackTrace();
showErrorMessage(e.getMessage());
}
}
return false;
}
@Override
public void onConnected() {
pushCommand("TRACK_GET");
}
@Override
public void onDisconnected(String msg) {
}
public void reconnectToServer() {
if(thread != null) {
thread.reconnect();
}
}
public boolean isLoggedIn() {
String psswd = app.getSettings().OSMO_USER_PWD.get();
String userName = app.getSettings().OSMO_USER_NAME.get();
return ((!TextUtils.isEmpty(psswd) && !TextUtils.isEmpty(userName)));
}
public OsmandApplication getMyApplication() {
return app;
}
}