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; + } +}