diff --git a/api/android/lib/AndroidManifest.xml b/api/android/lib/AndroidManifest.xml
new file mode 100644
index 0000000000..ed54f28c03
--- /dev/null
+++ b/api/android/lib/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/api/android/lib/build.xml b/api/android/lib/build.xml
new file mode 100644
index 0000000000..dbb259504a
--- /dev/null
+++ b/api/android/lib/build.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/android/lib/proguard-project.txt b/api/android/lib/proguard-project.txt
new file mode 100644
index 0000000000..f2fe1559a2
--- /dev/null
+++ b/api/android/lib/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/api/android/lib/project.properties b/api/android/lib/project.properties
new file mode 100644
index 0000000000..484dab0753
--- /dev/null
+++ b/api/android/lib/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
+android.library=true
diff --git a/api/android/lib/src/com/mapswithme/maps/api/Const.java b/api/android/lib/src/com/mapswithme/maps/api/Const.java
new file mode 100644
index 0000000000..b9f6195fc4
--- /dev/null
+++ b/api/android/lib/src/com/mapswithme/maps/api/Const.java
@@ -0,0 +1,37 @@
+
+package com.mapswithme.maps.api;
+
+public class Const
+{
+ /* Codes */
+ // status
+ public static final int STATUS_OK = 0;
+ public static final int STATUS_CANCEL = 1;
+
+
+ /* Request extras */
+ static final String AUTHORITY = "com.mapswithme.maps.api";
+ public static final String EXTRA_URL = AUTHORITY + ".url";
+ public static final String EXTRA_TITLE = AUTHORITY + ".title";
+ public static final String EXTRA_CALLMEBACK_MODE = AUTHORITY + ".callmeback_mode";
+ public static final String EXTRA_CALLBACK_ACTION = AUTHORITY + ".callback";
+ public static final String EXTRA_API_VERSION = AUTHORITY + ".version";
+ public static final String EXTRA_CALLER_APP_INFO = AUTHORITY + ".caller_app_info";
+
+
+ /* Response extras */
+ public static final String EXTRA_MWM_RESPONSE_STATUS = AUTHORITY + ".status";
+ /* Point part-by-part*/
+ public static final String EXTRA_MWM_RESPONSE_HAS_POINT = AUTHORITY + ".has_point";
+ public static final String EXTRA_MWM_RESPONSE_POINT_NAME = AUTHORITY + ".point_name";
+ public static final String EXTRA_MWM_RESPONSE_POINT_LAT = AUTHORITY + ".point_lat";
+ public static final String EXTRA_MWM_RESPONSE_POINT_LON = AUTHORITY + ".point_lon";
+ public static final String EXTRA_MWM_RESPONSE_POINT_ID = AUTHORITY + ".point_id";
+
+
+ static final int API_VERSION = 1;
+ static final String CALLBACK_PREFIX = "mapswithme.client.";
+ static final String ACTION_MWM_REQUEST = AUTHORITY + ".request";
+
+ private Const() {}
+}
diff --git a/api/android/lib/src/com/mapswithme/maps/api/MWMPoint.java b/api/android/lib/src/com/mapswithme/maps/api/MWMPoint.java
new file mode 100644
index 0000000000..2715035d87
--- /dev/null
+++ b/api/android/lib/src/com/mapswithme/maps/api/MWMPoint.java
@@ -0,0 +1,77 @@
+package com.mapswithme.maps.api;
+
+import java.io.Serializable;
+
+// TODO add javadoc for public interface
+public final class MWMPoint implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ final private double mLat;
+ final private double mLon;
+ final private String mName;
+ private String mId;
+
+ public MWMPoint(double lat, double lon, String name)
+ {
+ this(lat, lon, name, null);
+ }
+
+ public MWMPoint(double lat, double lon, String name, String id)
+ {
+ this.mLat = lat;
+ this.mLon = lon;
+ this.mName = name;
+ this.mId = id;
+ }
+
+ public double getLat() { return mLat; }
+ public double getLon() { return mLon; }
+ public String getName() { return mName; }
+ public String getId() { return mId; }
+
+ public void setId(String id) { mId = id; }
+
+ @Override
+ public String toString()
+ {
+ return "MWMPoint [mLat=" + mLat + ", mLon=" + mLon + ", mName=" + mName + ", mId=" + mId + "]";
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ long temp;
+ temp = Double.doubleToLongBits(mLat);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(mLon);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ result = prime * result + ((mName == null) ? 0 : mName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final MWMPoint other = (MWMPoint) obj;
+ if (Double.doubleToLongBits(mLat) != Double.doubleToLongBits(other.mLat))
+ return false;
+ if (Double.doubleToLongBits(mLon) != Double.doubleToLongBits(other.mLon))
+ return false;
+ if (mName == null)
+ {
+ if (other.mName != null)
+ return false;
+ } else if (!mName.equals(other.mName))
+ return false;
+ return true;
+ }
+}
diff --git a/api/android/lib/src/com/mapswithme/maps/api/MWMResponse.java b/api/android/lib/src/com/mapswithme/maps/api/MWMResponse.java
new file mode 100644
index 0000000000..3fe8410f61
--- /dev/null
+++ b/api/android/lib/src/com/mapswithme/maps/api/MWMResponse.java
@@ -0,0 +1,42 @@
+package com.mapswithme.maps.api;
+
+import android.content.Context;
+import android.content.Intent;
+
+// TODO add javadoc for public interface
+public class MWMResponse
+{
+ private int mStatus;
+ private MWMPoint mPoint;
+
+ public boolean isCanceled() { return Const.STATUS_CANCEL == mStatus; }
+ public boolean isSuccessful() { return Const.STATUS_OK == mStatus; }
+
+ public MWMPoint getPoint() { return mPoint; }
+ public boolean hasPoint() { return mPoint != null; }
+
+ @Override
+ public String toString()
+ {
+ return "MWMResponse [mStatus=" + mStatus + ", mSelectedPoint=" + mPoint + "]";
+ }
+
+ static MWMResponse extractFromIntent(Context context, Intent intent)
+ {
+ final MWMResponse response = new MWMResponse();
+ // parse status
+ response.mStatus = intent.getIntExtra(Const.EXTRA_MWM_RESPONSE_STATUS, Const.STATUS_OK);
+ // parse point
+ if (intent.getBooleanExtra(Const.EXTRA_MWM_RESPONSE_HAS_POINT, false))
+ {
+ final double lat = intent.getDoubleExtra(Const.EXTRA_MWM_RESPONSE_POINT_LAT, 0);
+ final double lon = intent.getDoubleExtra(Const.EXTRA_MWM_RESPONSE_POINT_LON, 0);
+ final String name = intent.getStringExtra(Const.EXTRA_MWM_RESPONSE_POINT_NAME);
+ final String id = intent.getStringExtra(Const.EXTRA_MWM_RESPONSE_POINT_ID);
+ response.mPoint = new MWMPoint(lat, lon, name, id);
+ }
+ return response;
+ }
+
+ private MWMResponse() {}
+}
diff --git a/api/android/lib/src/com/mapswithme/maps/api/MWMResponseHandler.java b/api/android/lib/src/com/mapswithme/maps/api/MWMResponseHandler.java
new file mode 100644
index 0000000000..e938619705
--- /dev/null
+++ b/api/android/lib/src/com/mapswithme/maps/api/MWMResponseHandler.java
@@ -0,0 +1,9 @@
+package com.mapswithme.maps.api;
+
+import android.content.Context;
+
+// TODO add javadoc
+public interface MWMResponseHandler
+{
+ public void onResponse(Context context, MWMResponse response);
+}
diff --git a/api/android/lib/src/com/mapswithme/maps/api/MWMResponseReciever.java b/api/android/lib/src/com/mapswithme/maps/api/MWMResponseReciever.java
new file mode 100644
index 0000000000..8ec37a2cdb
--- /dev/null
+++ b/api/android/lib/src/com/mapswithme/maps/api/MWMResponseReciever.java
@@ -0,0 +1,28 @@
+
+package com.mapswithme.maps.api;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+
+// TODO add javadoc with example
+public final class MWMResponseReciever extends BroadcastReceiver
+{
+ // I believe this it not the best approach
+ // But we leave it to keep things simple
+ static MWMResponseHandler sResponseHandler;
+
+ @Override
+ final public void onReceive(Context context, Intent intent)
+ {
+ if ( MapsWithMeApi.getCallbackAction(context).equals(intent.getAction())
+ && sResponseHandler != null)
+ {
+ sResponseHandler.onResponse(context, MWMResponse.extractFromIntent(context, intent));
+ // clean up handler to avoid context-leak
+ sResponseHandler = null;
+ }
+ }
+
+}
diff --git a/api/android/lib/src/com/mapswithme/maps/api/MapsWithMeApi.java b/api/android/lib/src/com/mapswithme/maps/api/MapsWithMeApi.java
new file mode 100644
index 0000000000..7f8e680718
--- /dev/null
+++ b/api/android/lib/src/com/mapswithme/maps/api/MapsWithMeApi.java
@@ -0,0 +1,129 @@
+
+package com.mapswithme.maps.api;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.widget.Toast;
+
+import java.util.Locale;
+
+//TODO add javadoc for public interface
+public final class MapsWithMeApi
+{
+
+ public static void showPointsOnMap(Activity caller, MWMPoint... points)
+ {
+ showPointsOnMap(caller, null, null, points);
+ }
+
+ public static void showPointOnMap(Activity caller, double lat, double lon, String name, String id)
+ {
+ showPointsOnMap(caller, (String)null, (MWMResponseHandler)null, new MWMPoint(lat, lon, name));
+ }
+
+ public static void showPointsOnMap(Activity caller, String title, MWMPoint... points)
+ {
+ showPointsOnMap(caller, title, null, points);
+ }
+
+ public static void showPointsOnMap(Activity caller, String title, MWMResponseHandler responseHandler, MWMPoint... points)
+ {
+ final Intent mwmIntent = new Intent(Const.ACTION_MWM_REQUEST);
+ mwmIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+ mwmIntent.putExtra(Const.EXTRA_URL, createMwmUrl(caller, title, points).toString());
+ mwmIntent.putExtra(Const.EXTRA_TITLE, title);
+ mwmIntent.putExtra(Const.EXTRA_CALLMEBACK_MODE, responseHandler != null);
+
+ addCommonExtras(caller, mwmIntent);
+
+ MWMResponseReciever.sResponseHandler = responseHandler;
+ if (responseHandler != null)
+ {
+ // detect if it is registered
+ // throw if not
+ final Intent callbackIntentStub = new Intent(getCallbackAction(caller));
+ final boolean recieverEnabled = caller.getPackageManager().queryBroadcastReceivers(callbackIntentStub, 0).size() > 0;
+ if (!recieverEnabled)
+ throw new IllegalStateException(String.format(
+ "BroadcastReciever with intent-filter for action \"%s\" must be added to manifest.", getCallbackAction(caller)));
+ }
+
+ if (isMapsWithMeInstalled(caller))
+ {
+ // Match activity for intent
+ // TODO specify DEFAULT for Pro version.
+ final ActivityInfo aInfo = caller.getPackageManager().resolveActivity(mwmIntent, 0).activityInfo;
+ mwmIntent.setClassName(aInfo.packageName, aInfo.name);
+ caller.startActivity(mwmIntent);
+ }
+ //TODO this is temporally solution, add dialog
+ else
+ Toast.makeText(caller, "MapsWithMe is not installed.", Toast.LENGTH_LONG).show();
+ }
+
+ public static boolean isMapsWithMeInstalled(Context context)
+ {
+ final Intent intent = new Intent(Const.ACTION_MWM_REQUEST);
+ return context.getPackageManager().resolveActivity(intent, 0) != null;
+ }
+
+
+ static StringBuilder createMwmUrl(Context context, String title, MWMPoint ... points)
+ {
+ StringBuilder urlBuilder = new StringBuilder("mapswithme://map?");
+ // version
+ urlBuilder.append("v=")
+ .append(Const.API_VERSION)
+ .append("&");
+ // back url, always not null
+ urlBuilder.append("backurl=")
+ .append(getCallbackAction(context))
+ .append("&");
+ // title
+ appendIfNotNull(urlBuilder, "appname", title);
+
+ // points
+ for (MWMPoint point : points)
+ {
+ if (point != null)
+ {
+ urlBuilder.append("ll=")
+ .append(String.format(Locale.US, "%f,%f&", point.getLat(), point.getLon()));
+
+ appendIfNotNull(urlBuilder, "n", point.getName());
+ appendIfNotNull(urlBuilder, "u", point.getId());
+ }
+ }
+
+ return urlBuilder;
+ }
+
+ static String getCallbackAction(Context context)
+ {
+ return Const.CALLBACK_PREFIX + context.getPackageName();
+ }
+
+ private static Intent addCommonExtras(Context context, Intent intent)
+ {
+ intent.putExtra(Const.EXTRA_CALLER_APP_INFO, context.getApplicationInfo());
+ intent.putExtra(Const.EXTRA_API_VERSION, Const.API_VERSION);
+ intent.putExtra(Const.EXTRA_CALLBACK_ACTION, getCallbackAction(context));
+
+ return intent;
+ }
+
+ private static StringBuilder appendIfNotNull(StringBuilder builder, String key, String value)
+ {
+ if (value != null)
+ builder.append(key)
+ .append("=")
+ .append(Uri.encode(value))
+ .append("&");
+
+ return builder;
+ }
+}