Merge branch 'master' into fix_add_photos_button
# Conflicts: # OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java
This commit is contained in:
commit
ad6c0c420f
23 changed files with 2047 additions and 225 deletions
|
@ -56,6 +56,55 @@ public class NetworkUtils {
|
||||||
return e.getMessage();
|
return e.getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String sendPostDataRequest(String urlText, InputStream data) {
|
||||||
|
try {
|
||||||
|
log.info("POST : " + urlText);
|
||||||
|
HttpURLConnection conn = getHttpURLConnection(urlText);
|
||||||
|
conn.setDoInput(true);
|
||||||
|
conn.setDoOutput(false);
|
||||||
|
conn.setRequestMethod("POST");
|
||||||
|
conn.setRequestProperty("Accept", "*/*");
|
||||||
|
conn.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
|
||||||
|
OutputStream ous = conn.getOutputStream();
|
||||||
|
ous.write(("--" + BOUNDARY + "\r\n").getBytes());
|
||||||
|
ous.write(("content-disposition: form-data; name=\"" + "file" + "\"; filename=\"" + "image1" + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$
|
||||||
|
Algorithms.streamCopy(data, ous);
|
||||||
|
ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
ous.flush();
|
||||||
|
log.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage());
|
||||||
|
if (conn.getResponseCode() != 200) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuilder responseBody = new StringBuilder();
|
||||||
|
InputStream is = conn.getInputStream();
|
||||||
|
responseBody.setLength(0);
|
||||||
|
if (is != null) {
|
||||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); //$NON-NLS-1$
|
||||||
|
String s;
|
||||||
|
boolean first = true;
|
||||||
|
while ((s = in.readLine()) != null) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
responseBody.append("\n"); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
responseBody.append(s);
|
||||||
|
}
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
Algorithms.closeStream(is);
|
||||||
|
Algorithms.closeStream(data);
|
||||||
|
Algorithms.closeStream(ous);
|
||||||
|
return responseBody.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$
|
private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$
|
||||||
public static String uploadFile(String urlText, File fileToUpload, String userNamePassword,
|
public static String uploadFile(String urlText, File fileToUpload, String userNamePassword,
|
||||||
OsmOAuthAuthorizationClient client,
|
OsmOAuthAuthorizationClient client,
|
||||||
|
|
|
@ -507,4 +507,6 @@ dependencies {
|
||||||
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
||||||
|
|
||||||
freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300'
|
freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300'
|
||||||
|
|
||||||
|
implementation "org.bouncycastle:bcpkix-jdk15on:1.56"
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,18 +26,27 @@
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textSize="@dimen/default_desc_text_size"
|
android:textSize="@dimen/default_desc_text_size"
|
||||||
android:textColor="@drawable/radio_flat_text_selector_light"
|
|
||||||
osmand:typeface="@string/font_roboto_medium"
|
osmand:typeface="@string/font_roboto_medium"
|
||||||
tools:text="@string/shared_string_left"/>
|
tools:text="@string/shared_string_left"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/buttons_divider"
|
android:id="@+id/center_button_container"
|
||||||
android:layout_width="1dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="0"
|
android:layout_weight="1"
|
||||||
android:background="?attr/divider_color">
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<net.osmand.plus.widgets.TextViewEx
|
||||||
|
android:id="@+id/center_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="@dimen/default_desc_text_size"
|
||||||
|
osmand:typeface="@string/font_roboto_medium"
|
||||||
|
tools:text="@string/position_on_map_center"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
@ -53,9 +62,8 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="@drawable/radio_flat_text_selector_light"
|
|
||||||
osmand:typeface="@string/font_roboto_medium"
|
|
||||||
android:textSize="@dimen/default_desc_text_size"
|
android:textSize="@dimen/default_desc_text_size"
|
||||||
|
osmand:typeface="@string/font_roboto_medium"
|
||||||
tools:text="@string/shared_string_right"/>
|
tools:text="@string/shared_string_right"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -30,23 +30,22 @@
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/up_down_row"
|
android:id="@+id/up_down_row"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/measurement_tool_up_down_row_height"
|
||||||
android:minHeight="112dp"
|
|
||||||
android:background="?attr/selectableItemBackground">
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/main_icon"
|
android:id="@+id/main_icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_marginStart="@dimen/measurement_tool_text_button_padding"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_marginLeft="@dimen/measurement_tool_text_button_padding"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginTop="@dimen/bottom_sheet_icon_margin"
|
|
||||||
android:layout_marginEnd="@dimen/measurement_tool_text_button_padding"
|
android:layout_marginEnd="@dimen/measurement_tool_text_button_padding"
|
||||||
|
android:layout_marginLeft="@dimen/measurement_tool_text_button_padding"
|
||||||
android:layout_marginRight="@dimen/measurement_tool_text_button_padding"
|
android:layout_marginRight="@dimen/measurement_tool_text_button_padding"
|
||||||
|
android:layout_marginStart="@dimen/measurement_tool_text_button_padding"
|
||||||
android:background="@null"
|
android:background="@null"
|
||||||
tools:src="@drawable/ic_action_ruler" />
|
tools:src="@drawable/ic_action_ruler"/>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/up_down_button"
|
android:id="@+id/up_down_button"
|
||||||
|
@ -54,7 +53,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_marginTop="@dimen/bottom_sheet_icon_margin"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginEnd="@dimen/bottom_sheet_content_margin"
|
android:layout_marginEnd="@dimen/bottom_sheet_content_margin"
|
||||||
android:layout_marginLeft="@dimen/bottom_sheet_content_margin"
|
android:layout_marginLeft="@dimen/bottom_sheet_content_margin"
|
||||||
android:layout_marginRight="@dimen/bottom_sheet_content_margin"
|
android:layout_marginRight="@dimen/bottom_sheet_content_margin"
|
||||||
|
@ -129,17 +128,6 @@
|
||||||
android:textAppearance="@style/TextAppearance.ListItemTitle"
|
android:textAppearance="@style/TextAppearance.ListItemTitle"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:text="@string/add_point_after"/>
|
tools:text="@string/add_point_after"/>
|
||||||
|
|
||||||
<include
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/measurement_tool_button_height"
|
|
||||||
android:layout_below="@id/distance_to_center_text_view"
|
|
||||||
android:layout_marginTop="@dimen/content_padding_half"
|
|
||||||
android:layout_marginBottom="@dimen/measurement_tool_content_padding_medium"
|
|
||||||
android:layout_marginStart="@dimen/content_padding"
|
|
||||||
android:layout_marginEnd="@dimen/content_padding"
|
|
||||||
layout="@layout/custom_radio_buttons" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -149,6 +137,8 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="gone" >
|
android:visibility="gone" >
|
||||||
|
|
||||||
|
<include layout="@layout/custom_radio_buttons" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/content_padding_small" />
|
android:layout_height="@dimen/content_padding_small" />
|
||||||
|
@ -160,11 +150,6 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="?attr/dashboard_divider" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/measure_mode_controls"
|
android:id="@+id/measure_mode_controls"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/bg_color"
|
android:background="?attr/bg_color"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
|
@ -26,55 +26,55 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/toolbar"
|
android:layout_below="@id/toolbar"
|
||||||
android:layout_above="@id/buttons">
|
android:layout_above="@id/buttons">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/opr_img"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/shared_string_back"
|
||||||
|
app:srcCompat="@drawable/ic_img_logo_openplacereview" />
|
||||||
|
|
||||||
|
<net.osmand.plus.widgets.TextViewEx
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:layout_marginLeft="@dimen/content_padding"
|
||||||
|
android:layout_marginTop="@dimen/content_padding"
|
||||||
|
android:layout_marginRight="@dimen/content_padding"
|
||||||
|
android:layout_marginBottom="@dimen/dashPadding"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
|
||||||
|
android:text="@string/register_on_openplacereviews"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:typeface="@string/font_roboto_medium" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/opr_img"
|
android:id="@+id/start_opr_description"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_marginLeft="@dimen/content_padding"
|
||||||
android:contentDescription="@string/shared_string_back"
|
android:layout_marginTop="@dimen/dashPadding"
|
||||||
app:srcCompat="@drawable/ic_img_logo_openplacereview" />
|
android:layout_marginRight="@dimen/content_padding"
|
||||||
|
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
|
||||||
<net.osmand.plus.widgets.TextViewEx
|
app:typeface="@string/font_roboto_regular"
|
||||||
android:layout_width="match_parent"
|
android:text="@string/register_on_openplacereviews_desc"
|
||||||
android:layout_height="wrap_content"
|
android:textColor="?android:textColorPrimary"
|
||||||
android:layout_marginLeft="@dimen/content_padding"
|
android:textSize="@dimen/default_list_text_size"
|
||||||
android:layout_marginTop="@dimen/content_padding"
|
android:textColorLink="@color/icon_color_active_light" />
|
||||||
android:layout_marginRight="@dimen/content_padding"
|
</LinearLayout>
|
||||||
android:layout_marginBottom="@dimen/dashPadding"
|
</androidx.core.widget.NestedScrollView>
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
|
|
||||||
android:text="@string/register_on_openplacereviews"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?android:textColorPrimary"
|
|
||||||
android:textSize="20sp"
|
|
||||||
app:typeface="@string/font_roboto_medium" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/start_opr_description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/content_padding"
|
|
||||||
android:layout_marginTop="@dimen/dashPadding"
|
|
||||||
android:layout_marginRight="@dimen/content_padding"
|
|
||||||
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
|
|
||||||
app:typeface="@string/font_roboto_regular"
|
|
||||||
android:text="@string/register_on_openplacereviews_desc"
|
|
||||||
android:textColor="?android:textColorPrimary"
|
|
||||||
android:textSize="@dimen/default_list_text_size"
|
|
||||||
android:textColorLink="@color/icon_color_active_light" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/buttons"
|
android:id="@+id/buttons"
|
||||||
|
|
|
@ -11,8 +11,11 @@
|
||||||
Thx - Hardy
|
Thx - Hardy
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
<string name="add_photos_descr">OsmAnd shows photos from several sources:\nOpenPlaceReviews - POI photos;\nMapillary - street-level imagery;\nWeb / Wikimedia - POI photos specified in OpenStreetMap data.</string>
|
||||||
<string name="use_dev_url">Use dev.openstreetmap.org</string>
|
<string name="use_dev_url">Use dev.openstreetmap.org</string>
|
||||||
<string name="use_dev_url_descr">Switch to use "dev.openstreetmap.org" instead of "openstreetmap.org" to testing uploading OSM Note / POI / GPX.</string>
|
<string name="use_dev_url_descr">Switch to use "dev.openstreetmap.org" instead of "openstreetmap.org" to testing uploading OSM Note / POI / GPX.</string>
|
||||||
|
<string name="add_to_opr">Add to OpenPlaceReviews</string>
|
||||||
|
<string name="add_to_mapillary">Add to Mapillary</string>
|
||||||
<string name="select_items_for_import">Select items that will be imported.</string>
|
<string name="select_items_for_import">Select items that will be imported.</string>
|
||||||
<string name="select_groups_for_import">Select groups that will be imported.</string>
|
<string name="select_groups_for_import">Select groups that will be imported.</string>
|
||||||
<string name="export_not_enough_space">There is not enough space</string>
|
<string name="export_not_enough_space">There is not enough space</string>
|
||||||
|
@ -21,18 +24,17 @@
|
||||||
<string name="select_data_to_export">Select the data to be exported to the file.</string>
|
<string name="select_data_to_export">Select the data to be exported to the file.</string>
|
||||||
<string name="approximate_file_size">Approximate file size</string>
|
<string name="approximate_file_size">Approximate file size</string>
|
||||||
<string name="shared_string_resources">Resources</string>
|
<string name="shared_string_resources">Resources</string>
|
||||||
<string name="add_photos_descr">OsmAnd shows photos from several sources:\nOpenPlaceReviews - POI photos;\nMapillary - street-level imagery;\nWeb / Wikimedia - POI photos specified in OpenStreetMap data.</string>
|
<string name="select_picture">Select picture</string>
|
||||||
<string name="add_to_opr">Add to OpenPlaceReviews</string>
|
<string name="cannot_upload_image">Cannot upload image, please, try again later</string>
|
||||||
<string name="add_to_mapillary">Add to Mapillary</string>
|
|
||||||
<string name="app_mode_motorboat">Motorboat</string>
|
<string name="app_mode_motorboat">Motorboat</string>
|
||||||
<string name="app_mode_kayak">Kayak</string>
|
<string name="app_mode_kayak">Kayak</string>
|
||||||
<string name="shared_string_search_history">Search history</string>
|
<string name="shared_string_search_history">Search history</string>
|
||||||
<string name="register_opr_have_account">I already have an account</string>
|
<string name="register_opr_have_account">I already have an account</string>
|
||||||
<string name="register_opr_create_new_account">Create new account</string>
|
<string name="register_opr_create_new_account">Create new account</string>
|
||||||
<string name="register_on_openplacereviews_desc">Log in on the open data project website OpenPlaceReviews.org to upload even more photos.</string>
|
<string name="register_on_openplacereviews_desc">Photos are provided by open data project OpenPlaceReviews.org. In order to upload your photos you need to sign up on website.</string>
|
||||||
<string name="register_on_openplacereviews">Register on\nOpenPlaceReviews.org</string>
|
<string name="register_on_openplacereviews">Register on\nOpenPlaceReviews.org</string>
|
||||||
<string name="shared_string_add_photo">Add photo</string>
|
<string name="shared_string_add_photo">Add photo</string>
|
||||||
<string name="osm_login_descr">Log in using the safe OAuth method or use your username and password.</string>
|
<string name="osm_login_descr">You can log in using the safe OAuth method or use your login and password.</string>
|
||||||
<string name="osm_edit_comment_note">Comment OSM Note</string>
|
<string name="osm_edit_comment_note">Comment OSM Note</string>
|
||||||
<string name="osm_edit_close_note">Close OSM Note</string>
|
<string name="osm_edit_close_note">Close OSM Note</string>
|
||||||
<string name="gpx_upload_trackable_visibility_descr">\"Trackable\" means the trace does not show up in any public listings, but processed trackpoints with timestamps from it (that can\'t be associated with you directly) do through downloads from the public GPS API.</string>
|
<string name="gpx_upload_trackable_visibility_descr">\"Trackable\" means the trace does not show up in any public listings, but processed trackpoints with timestamps from it (that can\'t be associated with you directly) do through downloads from the public GPS API.</string>
|
||||||
|
@ -47,15 +49,15 @@
|
||||||
<string name="subscription_expired_title">OsmAnd Live subscription has been expired</string>
|
<string name="subscription_expired_title">OsmAnd Live subscription has been expired</string>
|
||||||
<string name="subscription_payment_issue_title">There is a problem with your subscription. Click the button to go to the Google Play subscription settings to fix your payment method.</string>
|
<string name="subscription_payment_issue_title">There is a problem with your subscription. Click the button to go to the Google Play subscription settings to fix your payment method.</string>
|
||||||
<string name="manage_subscription">Manage subscription</string>
|
<string name="manage_subscription">Manage subscription</string>
|
||||||
<string name="user_login">Username</string>
|
<string name="user_login">Login</string>
|
||||||
<string name="user_password">Password</string>
|
<string name="user_password">Password</string>
|
||||||
<string name="login_account">Account</string>
|
<string name="login_account">Account</string>
|
||||||
<string name="use_login_password">Log in with username and password</string>
|
<string name="use_login_password">Use login and password</string>
|
||||||
<string name="open_street_map_login_mode">Log in to upload new or modified changes,\n\neither with OAuth or using your username and password.</string>
|
<string name="open_street_map_login_mode">You need to login to upload new or modified changes. \n\nYou can log in using the safe OAuth method or use your login and password.</string>
|
||||||
<string name="osm_edits_view_descr">You can view all your not yet uploaded edits or OSM bugs in %1$s. Uploaded points don’t show in OsmAnd.</string>
|
<string name="osm_edits_view_descr">You can view all your not yet uploaded edits or OSM bugs in %1$s. Uploaded points don’t show in OsmAnd.</string>
|
||||||
<string name="sign_in_with_open_street_map">Log in with OpenStreetMap</string>
|
<string name="sign_in_with_open_street_map">Sign in with OpenStreetMap</string>
|
||||||
<string name="login_open_street_map_org">Login for OpenStreetMap.org</string>
|
<string name="login_open_street_map_org">Login to OpenStreetMap.org</string>
|
||||||
<string name="login_open_street_map">Login for OpenStreetMap</string>
|
<string name="login_open_street_map">Login to OpenStreetMap</string>
|
||||||
<string name="plugin_global_prefs_info">These plugin setting are global, and apply to all profiles</string>
|
<string name="plugin_global_prefs_info">These plugin setting are global, and apply to all profiles</string>
|
||||||
<string name="message_you_need_add_two_points_to_show_graphs">You need to add at least two points</string>
|
<string name="message_you_need_add_two_points_to_show_graphs">You need to add at least two points</string>
|
||||||
<string name="icon_group_travel">Travel</string>
|
<string name="icon_group_travel">Travel</string>
|
||||||
|
|
|
@ -141,7 +141,7 @@ public class UiUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable getLayeredIcon(@DrawableRes int bgIconId, @DrawableRes int foregroundIconId,
|
public Drawable getLayeredIcon(@DrawableRes int bgIconId, @DrawableRes int foregroundIconId,
|
||||||
@ColorRes int bgColorId, @ColorRes int foregroundColorId) {
|
@ColorRes int bgColorId, @ColorRes int foregroundColorId) {
|
||||||
Drawable background = getDrawable(bgIconId, bgColorId);
|
Drawable background = getDrawable(bgIconId, bgColorId);
|
||||||
Drawable foreground = getDrawable(foregroundIconId, foregroundColorId);
|
Drawable foreground = getDrawable(foregroundIconId, foregroundColorId);
|
||||||
return getLayeredIcon(background, foreground);
|
return getLayeredIcon(background, foreground);
|
||||||
|
@ -272,11 +272,11 @@ public class UiUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLocationView(UpdateLocationViewCache cache, ImageView arrow, TextView txt,
|
public void updateLocationView(UpdateLocationViewCache cache, ImageView arrow, TextView txt,
|
||||||
double toLat, double toLon) {
|
double toLat, double toLon) {
|
||||||
updateLocationView(cache, arrow, txt, new LatLon(toLat, toLon));
|
updateLocationView(cache, arrow, txt, new LatLon(toLat, toLon));
|
||||||
}
|
}
|
||||||
public void updateLocationView(UpdateLocationViewCache cache, ImageView arrow, TextView txt,
|
public void updateLocationView(UpdateLocationViewCache cache, ImageView arrow, TextView txt,
|
||||||
LatLon toLoc) {
|
LatLon toLoc) {
|
||||||
float[] mes = new float[2];
|
float[] mes = new float[2];
|
||||||
boolean stale = false;
|
boolean stale = false;
|
||||||
LatLon fromLoc = cache == null ? null : cache.specialFrom;
|
LatLon fromLoc = cache == null ? null : cache.specialFrom;
|
||||||
|
@ -390,7 +390,7 @@ public class UiUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setupSnackbar(Snackbar snackbar, boolean nightMode, @ColorRes Integer backgroundColor,
|
public static void setupSnackbar(Snackbar snackbar, boolean nightMode, @ColorRes Integer backgroundColor,
|
||||||
@ColorRes Integer messageColor, @ColorRes Integer actionColor, Integer maxLines) {
|
@ColorRes Integer messageColor, @ColorRes Integer actionColor, Integer maxLines) {
|
||||||
if (snackbar == null) {
|
if (snackbar == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -451,7 +451,7 @@ public class UiUtilities {
|
||||||
|
|
||||||
|
|
||||||
public static void updateCustomRadioButtons(Context app, View buttonsView, boolean nightMode,
|
public static void updateCustomRadioButtons(Context app, View buttonsView, boolean nightMode,
|
||||||
CustomRadioButtonType buttonType) {
|
CustomRadioButtonType buttonType) {
|
||||||
int activeColor = ContextCompat.getColor(app, nightMode
|
int activeColor = ContextCompat.getColor(app, nightMode
|
||||||
? R.color.active_color_primary_dark
|
? R.color.active_color_primary_dark
|
||||||
: R.color.active_color_primary_light);
|
: R.color.active_color_primary_light);
|
||||||
|
@ -478,7 +478,7 @@ public class UiUtilities {
|
||||||
endButtonText.setTextColor(activeColor);
|
endButtonText.setTextColor(activeColor);
|
||||||
startButtonContainer.setBackgroundDrawable(background);
|
startButtonContainer.setBackgroundDrawable(background);
|
||||||
startButtonText.setTextColor(textColor);
|
startButtonText.setTextColor(textColor);
|
||||||
} else if (buttonType == CustomRadioButtonType.END) {
|
} else {
|
||||||
if (isLayoutRtl) {
|
if (isLayoutRtl) {
|
||||||
background.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});
|
background.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});
|
||||||
} else {
|
} else {
|
||||||
|
@ -488,11 +488,6 @@ public class UiUtilities {
|
||||||
endButtonText.setTextColor(textColor);
|
endButtonText.setTextColor(textColor);
|
||||||
startButtonContainer.setBackgroundColor(Color.TRANSPARENT);
|
startButtonContainer.setBackgroundColor(Color.TRANSPARENT);
|
||||||
startButtonText.setTextColor(activeColor);
|
startButtonText.setTextColor(activeColor);
|
||||||
} else if (buttonType == null) {
|
|
||||||
endButtonContainer.setBackgroundColor(Color.TRANSPARENT);
|
|
||||||
startButtonContainer.setBackgroundColor(Color.TRANSPARENT);
|
|
||||||
endButtonText.setTextColor(activeColor);
|
|
||||||
startButtonText.setTextColor(activeColor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,10 +502,10 @@ public class UiUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setupCompoundButton(boolean nightMode, @ColorInt int activeColor, CompoundButton compoundButton) {
|
public static void setupCompoundButton(boolean nightMode, @ColorInt int activeColor, CompoundButton compoundButton) {
|
||||||
if (compoundButton == null) {
|
if (compoundButton == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Context ctx = compoundButton.getContext();
|
Context ctx = compoundButton.getContext();
|
||||||
int inactiveColorPrimary = ContextCompat.getColor(ctx, nightMode ? R.color.icon_color_default_dark : R.color.icon_color_secondary_light);
|
int inactiveColorPrimary = ContextCompat.getColor(ctx, nightMode ? R.color.icon_color_default_dark : R.color.icon_color_secondary_light);
|
||||||
int inactiveColorSecondary = getColorWithAlpha(inactiveColorPrimary, 0.45f);
|
int inactiveColorSecondary = getColorWithAlpha(inactiveColorPrimary, 0.45f);
|
||||||
setupCompoundButton(compoundButton, activeColor, inactiveColorPrimary, inactiveColorSecondary);
|
setupCompoundButton(compoundButton, activeColor, inactiveColorPrimary, inactiveColorSecondary);
|
||||||
|
@ -589,7 +584,7 @@ public class UiUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setupSlider(Slider slider, boolean nightMode,
|
public static void setupSlider(Slider slider, boolean nightMode,
|
||||||
@ColorInt Integer activeColor, boolean showTicks) {
|
@ColorInt Integer activeColor, boolean showTicks) {
|
||||||
Context ctx = slider.getContext();
|
Context ctx = slider.getContext();
|
||||||
if (ctx == null) {
|
if (ctx == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -775,9 +770,9 @@ public class UiUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ListPopupWindow createListPopupWindow(Context themedCtx,
|
public static ListPopupWindow createListPopupWindow(Context themedCtx,
|
||||||
View v, int minWidth,
|
View v, int minWidth,
|
||||||
List<SimplePopUpMenuItem> items,
|
List<SimplePopUpMenuItem> items,
|
||||||
final AdapterView.OnItemClickListener listener) {
|
final AdapterView.OnItemClickListener listener) {
|
||||||
int contentPadding = themedCtx.getResources().getDimensionPixelSize(R.dimen.content_padding);
|
int contentPadding = themedCtx.getResources().getDimensionPixelSize(R.dimen.content_padding);
|
||||||
int contentPaddingHalf = themedCtx.getResources().getDimensionPixelSize(R.dimen.content_padding_half);
|
int contentPaddingHalf = themedCtx.getResources().getDimensionPixelSize(R.dimen.content_padding_half);
|
||||||
int defaultListTextSize = themedCtx.getResources().getDimensionPixelSize(R.dimen.default_list_text_size);
|
int defaultListTextSize = themedCtx.getResources().getDimensionPixelSize(R.dimen.default_list_text_size);
|
||||||
|
|
|
@ -376,7 +376,10 @@ public class SearchHistoryHelper {
|
||||||
SQLiteConnection db = openConnection(false);
|
SQLiteConnection db = openConnection(false);
|
||||||
if (db != null) {
|
if (db != null) {
|
||||||
try {
|
try {
|
||||||
removeQuery(e.getSerializedName(), db);
|
db.execSQL("DELETE FROM " + HISTORY_TABLE_NAME + " WHERE " +
|
||||||
|
HISTORY_COL_NAME + " = ? AND " +
|
||||||
|
HISTORY_COL_LAT + " = ? AND " + HISTORY_COL_LON + " = ?",
|
||||||
|
new Object[] {e.getSerializedName(), e.getLat(), e.getLon()});
|
||||||
} finally {
|
} finally {
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
@ -385,11 +388,6 @@ public class SearchHistoryHelper {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeQuery(String name, SQLiteConnection db) {
|
|
||||||
db.execSQL("DELETE FROM " + HISTORY_TABLE_NAME + " WHERE " + HISTORY_COL_NAME + " = ?",
|
|
||||||
new Object[]{name});
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeAll() {
|
public boolean removeAll() {
|
||||||
SQLiteConnection db = openConnection(false);
|
SQLiteConnection db = openConnection(false);
|
||||||
if (db != null) {
|
if (db != null) {
|
||||||
|
@ -411,9 +409,10 @@ public class SearchHistoryHelper {
|
||||||
"UPDATE " + HISTORY_TABLE_NAME + " SET " + HISTORY_COL_TIME + "= ? " +
|
"UPDATE " + HISTORY_TABLE_NAME + " SET " + HISTORY_COL_TIME + "= ? " +
|
||||||
", " + HISTORY_COL_FREQ_INTERVALS + " = ? " +
|
", " + HISTORY_COL_FREQ_INTERVALS + " = ? " +
|
||||||
", " + HISTORY_COL_FREQ_VALUES + "= ? WHERE " +
|
", " + HISTORY_COL_FREQ_VALUES + "= ? WHERE " +
|
||||||
HISTORY_COL_NAME + " = ?",
|
HISTORY_COL_NAME + " = ? AND " +
|
||||||
new Object[]{e.getLastAccessTime(), e.getIntervals(), e.getIntervalsValues(),
|
HISTORY_COL_LAT + " = ? AND " + HISTORY_COL_LON + " = ?",
|
||||||
e.getSerializedName()});
|
new Object[] {e.getLastAccessTime(), e.getIntervals(), e.getIntervalsValues(),
|
||||||
|
e.getSerializedName(), e.getLat(), e.getLon()});
|
||||||
} finally {
|
} finally {
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
@ -27,18 +29,27 @@ import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.view.ContextThemeWrapper;
|
import androidx.appcompat.view.ContextThemeWrapper;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.drawable.DrawableCompat;
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
|
|
||||||
import net.osmand.AndroidUtils;
|
import net.osmand.AndroidUtils;
|
||||||
|
import net.osmand.PlatformUtil;
|
||||||
import net.osmand.data.Amenity;
|
import net.osmand.data.Amenity;
|
||||||
import net.osmand.data.LatLon;
|
import net.osmand.data.LatLon;
|
||||||
import net.osmand.data.PointDescription;
|
import net.osmand.data.PointDescription;
|
||||||
import net.osmand.data.QuadRect;
|
import net.osmand.data.QuadRect;
|
||||||
import net.osmand.plus.*;
|
import net.osmand.osm.io.NetworkUtils;
|
||||||
|
import net.osmand.plus.OsmAndFormatter;
|
||||||
|
import net.osmand.plus.OsmandApplication;
|
||||||
|
import net.osmand.plus.OsmandPlugin;
|
||||||
|
import net.osmand.plus.R;
|
||||||
|
import net.osmand.plus.UiUtilities;
|
||||||
|
import net.osmand.plus.activities.ActivityResultListener;
|
||||||
import net.osmand.plus.activities.MapActivity;
|
import net.osmand.plus.activities.MapActivity;
|
||||||
import net.osmand.plus.helpers.FontCache;
|
import net.osmand.plus.helpers.FontCache;
|
||||||
import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard;
|
import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard;
|
||||||
|
@ -48,6 +59,9 @@ import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask
|
||||||
import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard;
|
import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard;
|
||||||
import net.osmand.plus.mapcontextmenu.controllers.TransportStopController;
|
import net.osmand.plus.mapcontextmenu.controllers.TransportStopController;
|
||||||
import net.osmand.plus.openplacereviews.AddPhotosBottomSheetDialogFragment;
|
import net.osmand.plus.openplacereviews.AddPhotosBottomSheetDialogFragment;
|
||||||
|
import net.osmand.plus.openplacereviews.OPRWebviewActivity;
|
||||||
|
import net.osmand.plus.openplacereviews.OprStartFragment;
|
||||||
|
import net.osmand.plus.osmedit.opr.OpenDBAPI;
|
||||||
import net.osmand.plus.poi.PoiUIFilter;
|
import net.osmand.plus.poi.PoiUIFilter;
|
||||||
import net.osmand.plus.render.RenderingIcons;
|
import net.osmand.plus.render.RenderingIcons;
|
||||||
import net.osmand.plus.transport.TransportStopRoute;
|
import net.osmand.plus.transport.TransportStopRoute;
|
||||||
|
@ -58,12 +72,27 @@ import net.osmand.plus.widgets.tools.ClickableSpanTouchListener;
|
||||||
import net.osmand.util.Algorithms;
|
import net.osmand.util.Algorithms;
|
||||||
import net.osmand.util.MapUtils;
|
import net.osmand.util.MapUtils;
|
||||||
|
|
||||||
import java.util.*;
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask.GetImageCardsListener;
|
import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask.GetImageCardsListener;
|
||||||
|
|
||||||
public class MenuBuilder {
|
public class MenuBuilder {
|
||||||
|
|
||||||
|
private static final int PICK_IMAGE = 1231;
|
||||||
|
private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class);
|
||||||
public static final float SHADOW_HEIGHT_TOP_DP = 17f;
|
public static final float SHADOW_HEIGHT_TOP_DP = 17f;
|
||||||
public static final int TITLE_LIMIT = 60;
|
public static final int TITLE_LIMIT = 60;
|
||||||
protected static final String[] arrowChars = new String[] {"=>", " - "};
|
protected static final String[] arrowChars = new String[] {"=>", " - "};
|
||||||
|
@ -93,6 +122,34 @@ public class MenuBuilder {
|
||||||
private String preferredMapAppLang;
|
private String preferredMapAppLang;
|
||||||
private boolean transliterateNames;
|
private boolean transliterateNames;
|
||||||
|
|
||||||
|
private final OpenDBAPI openDBAPI = new OpenDBAPI();
|
||||||
|
private String[] placeId = new String[0];
|
||||||
|
private GetImageCardsListener imageCardListener = new GetImageCardsListener() {
|
||||||
|
@Override
|
||||||
|
public void onPostProcess(List<ImageCard> cardList) {
|
||||||
|
processOnlinePhotosCards(cardList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaceIdAcquired(String[] placeId) {
|
||||||
|
MenuBuilder.this.placeId = placeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish(List<ImageCard> cardList) {
|
||||||
|
if (!isHidden()) {
|
||||||
|
List<AbstractCard> cards = new ArrayList<AbstractCard>(cardList);
|
||||||
|
if (cardList.size() == 0) {
|
||||||
|
cards.add(new NoImagesCard(mapActivity));
|
||||||
|
}
|
||||||
|
if (onlinePhotoCardsRow != null) {
|
||||||
|
onlinePhotoCardsRow.setCards(cards);
|
||||||
|
}
|
||||||
|
onlinePhotoCards = cards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public interface CollapseExpandListener {
|
public interface CollapseExpandListener {
|
||||||
void onCollapseExpand(boolean collapsed);
|
void onCollapseExpand(boolean collapsed);
|
||||||
}
|
}
|
||||||
|
@ -324,7 +381,37 @@ public class MenuBuilder {
|
||||||
button.setOnClickListener(new OnClickListener() {
|
button.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
AddPhotosBottomSheetDialogFragment.showInstance(mapActivity.getSupportFragmentManager());
|
if (false) {
|
||||||
|
AddPhotosBottomSheetDialogFragment.showInstance(mapActivity.getSupportFragmentManager());
|
||||||
|
} else {
|
||||||
|
registerResultListener(view);
|
||||||
|
final String baseUrl = OPRWebviewActivity.getBaseUrl(app);
|
||||||
|
final String name = OPRWebviewActivity.getUsernameFromCookie(app);
|
||||||
|
final String privateKey = OPRWebviewActivity.getPrivateKeyFromCookie(app);
|
||||||
|
if (privateKey == null || privateKey.isEmpty()) {
|
||||||
|
OprStartFragment.showInstance(mapActivity.getSupportFragmentManager());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (openDBAPI.checkPrivateKeyValid(baseUrl, name, privateKey)) {
|
||||||
|
app.runInUIThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setType("image/*");
|
||||||
|
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
|
mapActivity.startActivityForResult(Intent.createChooser(intent,
|
||||||
|
mapActivity.getString(R.string.select_picture)), PICK_IMAGE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
OprStartFragment.showInstance(mapActivity.getSupportFragmentManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//TODO This feature is under development
|
//TODO This feature is under development
|
||||||
|
@ -341,33 +428,86 @@ public class MenuBuilder {
|
||||||
false, null, false);
|
false, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerResultListener(final View view) {
|
||||||
|
mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, new ActivityResultListener.
|
||||||
|
OnActivityResultListener() {
|
||||||
|
@Override
|
||||||
|
public void onResult(int resultCode, Intent resultData) {
|
||||||
|
handleSelectedImage(view, resultData.getData());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSelectedImage(final View view, final Uri uri) {
|
||||||
|
Thread t = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = app.getContentResolver().openInputStream(uri);
|
||||||
|
if (inputStream != null) {
|
||||||
|
uploadImageToPlace(view, inputStream);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error(e);
|
||||||
|
} finally {
|
||||||
|
Algorithms.closeStream(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uploadImageToPlace(View view, InputStream image) {
|
||||||
|
InputStream serverData = new ByteArrayInputStream(compressImage(image));
|
||||||
|
final String baseUrl = OPRWebviewActivity.getBaseUrl(app);
|
||||||
|
String url = baseUrl + "api/ipfs/image";
|
||||||
|
String response = NetworkUtils.sendPostDataRequest(url, serverData);
|
||||||
|
if (response != null) {
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = openDBAPI.uploadImage(
|
||||||
|
placeId,
|
||||||
|
baseUrl,
|
||||||
|
OPRWebviewActivity.getPrivateKeyFromCookie(app),
|
||||||
|
OPRWebviewActivity.getUsernameFromCookie(app),
|
||||||
|
response);
|
||||||
|
} catch (FailedVerificationException e) {
|
||||||
|
LOG.error(e);
|
||||||
|
app.showToastMessage(R.string.cannot_upload_image);
|
||||||
|
}
|
||||||
|
if (res != 200) {
|
||||||
|
//image was uploaded but not added to blockchain
|
||||||
|
app.showToastMessage(R.string.cannot_upload_image);
|
||||||
|
} else {
|
||||||
|
app.showToastMessage(R.string.successfully_uploaded_pattern, 1, 1);
|
||||||
|
//refresh the image
|
||||||
|
execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.showToastMessage(R.string.cannot_upload_image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] compressImage(InputStream image) {
|
||||||
|
BufferedInputStream bufferedInputStream = new BufferedInputStream(image);
|
||||||
|
Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream);
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
bmp.compress(Bitmap.CompressFormat.PNG, 70, os);
|
||||||
|
return os.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
private void startLoadingImages() {
|
private void startLoadingImages() {
|
||||||
if (onlinePhotoCardsRow == null) {
|
if (onlinePhotoCardsRow == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
startLoadingImagesTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startLoadingImagesTask() {
|
||||||
onlinePhotoCards = new ArrayList<>();
|
onlinePhotoCards = new ArrayList<>();
|
||||||
onlinePhotoCardsRow.setProgressCard();
|
onlinePhotoCardsRow.setProgressCard();
|
||||||
execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(),
|
execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener));
|
||||||
new GetImageCardsListener() {
|
|
||||||
@Override
|
|
||||||
public void onPostProcess(List<ImageCard> cardList) {
|
|
||||||
processOnlinePhotosCards(cardList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinish(List<ImageCard> cardList) {
|
|
||||||
if (!isHidden()) {
|
|
||||||
List<AbstractCard> cards = new ArrayList<AbstractCard>(cardList);
|
|
||||||
if (cardList.size() == 0) {
|
|
||||||
cards.add(new NoImagesCard(mapActivity));
|
|
||||||
}
|
|
||||||
if (onlinePhotoCardsRow != null) {
|
|
||||||
onlinePhotoCardsRow.setCards(cards);
|
|
||||||
}
|
|
||||||
onlinePhotoCards = cards;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Map<String, String> getAdditionalCardParams() {
|
protected Map<String, String> getAdditionalCardParams() {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import net.osmand.plus.activities.MapActivity;
|
||||||
import net.osmand.plus.mapcontextmenu.MenuBuilder;
|
import net.osmand.plus.mapcontextmenu.MenuBuilder;
|
||||||
import net.osmand.plus.mapillary.MapillaryContributeCard;
|
import net.osmand.plus.mapillary.MapillaryContributeCard;
|
||||||
import net.osmand.plus.mapillary.MapillaryImageCard;
|
import net.osmand.plus.mapillary.MapillaryImageCard;
|
||||||
|
import net.osmand.plus.openplacereviews.OPRWebviewActivity;
|
||||||
import net.osmand.plus.wikimedia.WikiImageHelper;
|
import net.osmand.plus.wikimedia.WikiImageHelper;
|
||||||
import net.osmand.util.Algorithms;
|
import net.osmand.util.Algorithms;
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -408,6 +410,28 @@ public abstract class ImageCard extends AbstractCard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String[] getIdFromResponse(String response) {
|
||||||
|
try {
|
||||||
|
JSONArray obj = new JSONObject(response).getJSONArray("objects");
|
||||||
|
JSONArray images = (JSONArray) ((JSONObject) obj.get(0)).get("id");
|
||||||
|
return toStringArray(images);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] toStringArray(JSONArray array) {
|
||||||
|
if (array == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String[] arr = new String[array.length()];
|
||||||
|
for (int i = 0; i < arr.length; i++) {
|
||||||
|
arr[i] = array.optString(i);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
public static class GetImageCardsTask extends AsyncTask<Void, Void, List<ImageCard>> {
|
public static class GetImageCardsTask extends AsyncTask<Void, Void, List<ImageCard>> {
|
||||||
|
|
||||||
private MapActivity mapActivity;
|
private MapActivity mapActivity;
|
||||||
|
@ -421,11 +445,13 @@ public abstract class ImageCard extends AbstractCard {
|
||||||
public interface GetImageCardsListener {
|
public interface GetImageCardsListener {
|
||||||
void onPostProcess(List<ImageCard> cardList);
|
void onPostProcess(List<ImageCard> cardList);
|
||||||
|
|
||||||
|
void onPlaceIdAcquired(String[] placeId);
|
||||||
|
|
||||||
void onFinish(List<ImageCard> cardList);
|
void onFinish(List<ImageCard> cardList);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetImageCardsTask(@NonNull MapActivity mapActivity, LatLon latLon,
|
public GetImageCardsTask(@NonNull MapActivity mapActivity, LatLon latLon,
|
||||||
@Nullable Map<String, String> params, GetImageCardsListener listener) {
|
@Nullable Map<String, String> params, GetImageCardsListener listener) {
|
||||||
this.mapActivity = mapActivity;
|
this.mapActivity = mapActivity;
|
||||||
this.app = mapActivity.getMyApplication();
|
this.app = mapActivity.getMyApplication();
|
||||||
this.latLon = latLon;
|
this.latLon = latLon;
|
||||||
|
@ -441,7 +467,15 @@ public abstract class ImageCard extends AbstractCard {
|
||||||
if (o instanceof Amenity) {
|
if (o instanceof Amenity) {
|
||||||
Amenity am = (Amenity) o;
|
Amenity am = (Amenity) o;
|
||||||
long amenityId = am.getId() >> 1;
|
long amenityId = am.getId() >> 1;
|
||||||
getPicturesForPlace(result, amenityId);
|
String baseUrl = OPRWebviewActivity.getBaseUrl(app);
|
||||||
|
String url = baseUrl + "api/objects-by-index?type=opr.place&index=osmid&key=" + amenityId;
|
||||||
|
String response = AndroidNetworkUtils.sendRequest(app, url, Collections.<String, String>emptyMap(),
|
||||||
|
"Requesting location images...", false, false);
|
||||||
|
if (response != null) {
|
||||||
|
getPicturesForPlace(result, response);
|
||||||
|
String[] id = getIdFromResponse(response);
|
||||||
|
listener.onPlaceIdAcquired(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final Map<String, String> pms = new LinkedHashMap<>();
|
final Map<String, String> pms = new LinkedHashMap<>();
|
||||||
|
@ -503,26 +537,27 @@ public abstract class ImageCard extends AbstractCard {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getPicturesForPlace(List<ImageCard> result, long l) {
|
private void getPicturesForPlace(List<ImageCard> result, String response) {
|
||||||
String url = "https://test.openplacereviews.org/api/objects-by-index?type=opr.place&index=osmid&limit=1&key=" + l;
|
|
||||||
String response = AndroidNetworkUtils.sendRequest(app, url, Collections.<String, String>emptyMap(),
|
|
||||||
"Requesting location images...", false, false);
|
|
||||||
try {
|
try {
|
||||||
if (!Algorithms.isEmpty(response)) {
|
if (!Algorithms.isEmpty(response)) {
|
||||||
JSONArray obj = new JSONObject(response).getJSONArray("objects");
|
JSONArray obj = new JSONObject(response).getJSONArray("objects");
|
||||||
JSONArray images = ((JSONObject) ((JSONObject) obj.get(0)).get("images")).getJSONArray("outdoor");
|
JSONObject imagesWrapper = ((JSONObject) ((JSONObject) obj.get(0)).get("images"));
|
||||||
if (images.length() > 0) {
|
Iterator<String> it = imagesWrapper.keys();
|
||||||
for (int i = 0; i < images.length(); i++) {
|
while (it.hasNext()) {
|
||||||
try {
|
JSONArray images = imagesWrapper.getJSONArray(it.next());
|
||||||
JSONObject imageObject = (JSONObject) images.get(i);
|
if (images.length() > 0) {
|
||||||
if (imageObject != JSONObject.NULL) {
|
for (int i = 0; i < images.length(); i++) {
|
||||||
ImageCard imageCard = ImageCard.createCardOpr(mapActivity, imageObject);
|
try {
|
||||||
if (imageCard != null) {
|
JSONObject imageObject = (JSONObject) images.get(i);
|
||||||
result.add(imageCard);
|
if (imageObject != JSONObject.NULL) {
|
||||||
|
ImageCard imageCard = ImageCard.createCardOpr(mapActivity, imageObject);
|
||||||
|
if (imageCard != null) {
|
||||||
|
result.add(imageCard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LOG.error(e);
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
|
||||||
LOG.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,6 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter<HorizontalS
|
||||||
final HorizontalSelectionItem item = items.get(holder.getAdapterPosition());
|
final HorizontalSelectionItem item = items.get(holder.getAdapterPosition());
|
||||||
TextView textView = holder.buttonText;
|
TextView textView = holder.buttonText;
|
||||||
int activeColorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
|
int activeColorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
|
||||||
int innerPadding = AndroidUtils.dpToPx(app, 16);
|
|
||||||
textView.setPadding(innerPadding, 0, innerPadding, 0);
|
|
||||||
if (item.equals(selectedItem) && item.isEnabled()) {
|
if (item.equals(selectedItem) && item.isEnabled()) {
|
||||||
AndroidUtils.setBackground(holder.button, app.getUIUtilities().getPaintedIcon(
|
AndroidUtils.setBackground(holder.button, app.getUIUtilities().getPaintedIcon(
|
||||||
R.drawable.bg_select_icon_group_button, ContextCompat.getColor(app, activeColorResId)));
|
R.drawable.bg_select_icon_group_button, ContextCompat.getColor(app, activeColorResId)));
|
||||||
|
|
|
@ -77,10 +77,6 @@ public class TrackDetailsMenu {
|
||||||
private boolean hidding;
|
private boolean hidding;
|
||||||
private Location myLocation;
|
private Location myLocation;
|
||||||
|
|
||||||
public boolean shouldShowXAxisPoints () {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MapActivity getMapActivity() {
|
public MapActivity getMapActivity() {
|
||||||
return mapActivity;
|
return mapActivity;
|
||||||
|
@ -522,9 +518,7 @@ public class TrackDetailsMenu {
|
||||||
} else {
|
} else {
|
||||||
gpxItem.chartHighlightPos = -1;
|
gpxItem.chartHighlightPos = -1;
|
||||||
}
|
}
|
||||||
if (shouldShowXAxisPoints()) {
|
trackChartPoints.setXAxisPoints(getXAxisPoints(chart));
|
||||||
trackChartPoints.setXAxisPoints(getXAxisPoints(chart));
|
|
||||||
}
|
|
||||||
if (gpxItem.route) {
|
if (gpxItem.route) {
|
||||||
mapActivity.getMapLayers().getMapInfoLayer().setTrackChartPoints(trackChartPoints);
|
mapActivity.getMapLayers().getMapInfoLayer().setTrackChartPoints(trackChartPoints);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -127,8 +127,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
private Snackbar snackbar;
|
private Snackbar snackbar;
|
||||||
private String fileName;
|
private String fileName;
|
||||||
|
|
||||||
private @Nullable
|
private AdditionalInfoType currentAdditionalInfoType;
|
||||||
AdditionalInfoType currentAdditionalInfoType;
|
|
||||||
|
|
||||||
private boolean wasCollapseButtonVisible;
|
private boolean wasCollapseButtonVisible;
|
||||||
private boolean progressBarVisible;
|
private boolean progressBarVisible;
|
||||||
|
@ -176,11 +175,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
protected int getFragmentHeight() {
|
protected int getFragmentHeight() {
|
||||||
return mainView.getHeight();
|
return mainView.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldShowXAxisPoints() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setEditingCtx(MeasurementEditingContext editingCtx) {
|
private void setEditingCtx(MeasurementEditingContext editingCtx) {
|
||||||
|
@ -270,7 +264,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
portrait = AndroidUiHelper.isOrientationPortrait(mapActivity);
|
portrait = AndroidUiHelper.isOrientationPortrait(mapActivity);
|
||||||
|
|
||||||
pointsSt = getString(R.string.shared_string_gpx_points).toLowerCase();
|
pointsSt = getString(R.string.shared_string_gpx_points).toLowerCase();
|
||||||
int widthInPixels = getResources().getDimensionPixelOffset(R.dimen.gpx_group_button_width);
|
|
||||||
|
|
||||||
View view = UiUtilities.getInflater(getContext(), nightMode)
|
View view = UiUtilities.getInflater(getContext(), nightMode)
|
||||||
.inflate(R.layout.fragment_measurement_tool, container, false);
|
.inflate(R.layout.fragment_measurement_tool, container, false);
|
||||||
|
@ -290,15 +283,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
changeAdditionalInfoType(AdditionalInfoType.POINTS);
|
changeAdditionalInfoType(AdditionalInfoType.POINTS);
|
||||||
int pointsCount = editingCtx.getPointsCount();
|
|
||||||
if (pointsCount == 0) {
|
|
||||||
disable(upDownBtn);
|
|
||||||
collapseAdditionalInfoView();
|
|
||||||
} else {
|
|
||||||
expandAdditionalInfoView();
|
|
||||||
additionalInfoExpanded = true;
|
|
||||||
}
|
|
||||||
updateUpDownBtn();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -309,8 +293,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
changeAdditionalInfoType(AdditionalInfoType.GRAPH);
|
changeAdditionalInfoType(AdditionalInfoType.GRAPH);
|
||||||
expandAdditionalInfoView();
|
|
||||||
updateUpDownBtn();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -358,7 +340,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
View applyMovePointButton = mainView.findViewById(R.id.apply_move_point_button);
|
View applyMovePointButton = mainView.findViewById(R.id.apply_move_point_button);
|
||||||
UiUtilities.setupDialogButton(nightMode, applyMovePointButton, UiUtilities.DialogButtonType.PRIMARY,
|
UiUtilities.setupDialogButton(nightMode, applyMovePointButton, UiUtilities.DialogButtonType.PRIMARY,
|
||||||
R.string.shared_string_apply);
|
R.string.shared_string_apply);
|
||||||
applyMovePointButton.setMinimumWidth(widthInPixels);
|
|
||||||
applyMovePointButton.setOnClickListener(new OnClickListener() {
|
applyMovePointButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
@ -370,7 +351,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
View applyPointBeforeAfterButton = mainView.findViewById(R.id.apply_point_before_after_point_button);
|
View applyPointBeforeAfterButton = mainView.findViewById(R.id.apply_point_before_after_point_button);
|
||||||
UiUtilities.setupDialogButton(nightMode, applyPointBeforeAfterButton, UiUtilities.DialogButtonType.PRIMARY,
|
UiUtilities.setupDialogButton(nightMode, applyPointBeforeAfterButton, UiUtilities.DialogButtonType.PRIMARY,
|
||||||
R.string.shared_string_apply);
|
R.string.shared_string_apply);
|
||||||
applyPointBeforeAfterButton.setMinimumWidth(widthInPixels);
|
|
||||||
applyPointBeforeAfterButton.setOnClickListener(new OnClickListener() {
|
applyPointBeforeAfterButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
@ -381,7 +361,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
View addPointBeforeAfterButton = mainView.findViewById(R.id.add_point_before_after_button);
|
View addPointBeforeAfterButton = mainView.findViewById(R.id.add_point_before_after_button);
|
||||||
UiUtilities.setupDialogButton(nightMode, addPointBeforeAfterButton, UiUtilities.DialogButtonType.PRIMARY,
|
UiUtilities.setupDialogButton(nightMode, addPointBeforeAfterButton, UiUtilities.DialogButtonType.PRIMARY,
|
||||||
R.string.shared_string_add);
|
R.string.shared_string_add);
|
||||||
addPointBeforeAfterButton.setMinimumWidth(widthInPixels);
|
|
||||||
addPointBeforeAfterButton.setOnClickListener(new OnClickListener() {
|
addPointBeforeAfterButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
@ -437,7 +416,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
addCenterPoint();
|
addCenterPoint();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addPointButton.setMinimumWidth(widthInPixels);
|
|
||||||
measurementLayer.setOnSingleTapListener(new MeasurementToolLayer.OnSingleTapListener() {
|
measurementLayer.setOnSingleTapListener(new MeasurementToolLayer.OnSingleTapListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAddPoint() {
|
public void onAddPoint() {
|
||||||
|
@ -446,6 +425,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelectPoint(int selectedPointPos) {
|
public void onSelectPoint(int selectedPointPos) {
|
||||||
|
if (additionalInfoExpanded) {
|
||||||
|
collapseAdditionalInfoView();
|
||||||
|
}
|
||||||
if (selectedPointPos != -1) {
|
if (selectedPointPos != -1) {
|
||||||
openSelectedPointMenu(mapActivity);
|
openSelectedPointMenu(mapActivity);
|
||||||
}
|
}
|
||||||
|
@ -547,36 +529,25 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeAdditionalInfoType(@Nullable AdditionalInfoType type) {
|
private void changeAdditionalInfoType(@NonNull AdditionalInfoType type) {
|
||||||
if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) {
|
if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) {
|
||||||
MapActivity ma = getMapActivity();
|
MapActivity ma = getMapActivity();
|
||||||
if (ma == null) return;
|
if (ma == null) return;
|
||||||
|
|
||||||
OsmandApplication app = ma.getMyApplication();
|
OsmandApplication app = ma.getMyApplication();
|
||||||
View buttonsDivider = customRadioButton.findViewById(R.id.buttons_divider);
|
|
||||||
if (AdditionalInfoType.POINTS == type) {
|
if (AdditionalInfoType.POINTS == type) {
|
||||||
visibleCard = pointsCard;
|
visibleCard = pointsCard;
|
||||||
additionalInfoExpanded = true;
|
|
||||||
buttonsDivider.setVisibility(View.GONE);
|
|
||||||
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START);
|
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START);
|
||||||
} else if (AdditionalInfoType.GRAPH == type) {
|
} else if (AdditionalInfoType.GRAPH == type) {
|
||||||
visibleCard = graphsCard;
|
visibleCard = graphsCard;
|
||||||
additionalInfoExpanded = true;
|
|
||||||
buttonsDivider.setVisibility(View.GONE);
|
|
||||||
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END);
|
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END);
|
||||||
} else if (null == type) {
|
|
||||||
visibleCard = null;
|
|
||||||
additionalInfoExpanded = false;
|
|
||||||
buttonsDivider.setVisibility(View.VISIBLE);
|
|
||||||
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, null);
|
|
||||||
}
|
}
|
||||||
cardsContainer.removeAllViews();
|
cardsContainer.removeAllViews();
|
||||||
if (visibleCard != null) {
|
View cardView = visibleCard.getView() != null ? visibleCard.getView() : visibleCard.build(ma);
|
||||||
View cardView = visibleCard.getView() != null ? visibleCard.getView() : visibleCard.build(ma);
|
cardsContainer.addView(cardView);
|
||||||
cardsContainer.addView(cardView);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentAdditionalInfoType = type;
|
currentAdditionalInfoType = type;
|
||||||
|
additionalInfoExpanded = true;
|
||||||
updateUpDownBtn();
|
updateUpDownBtn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1059,7 +1030,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChangeApplicationMode(ApplicationMode mode, RouteBetweenPointsDialogType dialogType,
|
public void onChangeApplicationMode(ApplicationMode mode, RouteBetweenPointsDialogType dialogType,
|
||||||
RouteBetweenPointsDialogMode dialogMode) {
|
RouteBetweenPointsDialogMode dialogMode) {
|
||||||
MeasurementToolLayer measurementLayer = getMeasurementLayer();
|
MeasurementToolLayer measurementLayer = getMeasurementLayer();
|
||||||
if (measurementLayer != null) {
|
if (measurementLayer != null) {
|
||||||
ChangeRouteType changeRouteType = ChangeRouteType.NEXT_SEGMENT;
|
ChangeRouteType changeRouteType = ChangeRouteType.NEXT_SEGMENT;
|
||||||
|
@ -1494,10 +1465,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
private void collapseAdditionalInfoView() {
|
private void collapseAdditionalInfoView() {
|
||||||
if (portrait) {
|
if (portrait) {
|
||||||
additionalInfoExpanded = false;
|
additionalInfoExpanded = false;
|
||||||
|
updateUpDownBtn();
|
||||||
additionalInfoContainer.setVisibility(View.GONE);
|
additionalInfoContainer.setVisibility(View.GONE);
|
||||||
setDefaultMapPosition();
|
setDefaultMapPosition();
|
||||||
changeAdditionalInfoType(null);
|
|
||||||
updateUpDownBtn();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1896,7 +1866,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean showInstance(FragmentManager fragmentManager, MeasurementEditingContext editingCtx,
|
public static boolean showInstance(FragmentManager fragmentManager, MeasurementEditingContext editingCtx,
|
||||||
boolean followTrackMode) {
|
boolean followTrackMode) {
|
||||||
MeasurementToolFragment fragment = new MeasurementToolFragment();
|
MeasurementToolFragment fragment = new MeasurementToolFragment();
|
||||||
fragment.setEditingCtx(editingCtx);
|
fragment.setEditingCtx(editingCtx);
|
||||||
fragment.setMode(FOLLOW_TRACK_MODE, followTrackMode);
|
fragment.setMode(FOLLOW_TRACK_MODE, followTrackMode);
|
||||||
|
|
|
@ -27,16 +27,20 @@ public class OPRWebviewActivity extends OsmandActionBarActivity {
|
||||||
private WebView webView;
|
private WebView webView;
|
||||||
private boolean isLogin = false;
|
private boolean isLogin = false;
|
||||||
|
|
||||||
|
public static String getBaseUrl(Context ctx) {
|
||||||
|
return ctx.getString(R.string.opr_base_url);
|
||||||
|
}
|
||||||
|
|
||||||
public static String getCookieUrl(Context ctx) {
|
public static String getCookieUrl(Context ctx) {
|
||||||
return ctx.getString(R.string.opr_base_url) + "profile";
|
return getBaseUrl(ctx) + "profile";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getLoginUrl(Context ctx) {
|
public static String getLoginUrl(Context ctx) {
|
||||||
return ctx.getString(R.string.opr_base_url) + "login";
|
return getBaseUrl(ctx) + "login";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getRegisterUrl(Context ctx) {
|
public static String getRegisterUrl(Context ctx) {
|
||||||
return ctx.getString(R.string.opr_base_url) + "signup";
|
return getBaseUrl(ctx) + "signup";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFinishUrl(Context ctx) {
|
public static String getFinishUrl(Context ctx) {
|
||||||
|
|
|
@ -12,17 +12,14 @@ import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import net.osmand.PlatformUtil;
|
import net.osmand.PlatformUtil;
|
||||||
import net.osmand.plus.R;
|
import net.osmand.plus.R;
|
||||||
import net.osmand.plus.UiUtilities;
|
import net.osmand.plus.UiUtilities;
|
||||||
import net.osmand.plus.base.BaseOsmAndFragment;
|
import net.osmand.plus.base.BaseOsmAndFragment;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
||||||
public class OprStartFragment extends BaseOsmAndFragment {
|
public class OprStartFragment extends BaseOsmAndFragment {
|
||||||
|
|
152
OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java
Normal file
152
OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package net.osmand.plus.osmedit.opr;
|
||||||
|
|
||||||
|
import android.net.TrafficStats;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import net.osmand.PlatformUtil;
|
||||||
|
import net.osmand.osm.io.NetworkUtils;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.openplacereviews.opendb.SecUtils;
|
||||||
|
import org.openplacereviews.opendb.ops.OpOperation;
|
||||||
|
import org.openplacereviews.opendb.util.JsonFormatter;
|
||||||
|
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import static org.openplacereviews.opendb.SecUtils.ALGO_EC;
|
||||||
|
import static org.openplacereviews.opendb.SecUtils.JSON_MSG_TYPE;
|
||||||
|
import static org.openplacereviews.opendb.SecUtils.signMessageWithKeyBase64;
|
||||||
|
|
||||||
|
|
||||||
|
public class OpenDBAPI {
|
||||||
|
private static final Log log = PlatformUtil.getLog(SecUtils.class);
|
||||||
|
private static final String checkLoginEndpoint = "api/auth/user-check-loginkey?";
|
||||||
|
private static final String LOGIN_SUCCESS_MESSAGE = "{\"result\":\"OK\"}";
|
||||||
|
private static final int THREAD_ID = 11200;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* method for check if user is loggined in blockchain
|
||||||
|
* params
|
||||||
|
* - username: blockchain username in format "openplacereviews:test_1"
|
||||||
|
* - privatekey: "base64:PKCS#8:actualKey"
|
||||||
|
* Need to encode key
|
||||||
|
* Do not call on mainThread
|
||||||
|
*/
|
||||||
|
public boolean checkPrivateKeyValid(String baseUrl, String username, String privateKey) {
|
||||||
|
String url = null;
|
||||||
|
try {
|
||||||
|
url = baseUrl + checkLoginEndpoint +
|
||||||
|
"name=" +
|
||||||
|
username +
|
||||||
|
"&" +
|
||||||
|
"privateKey=" +
|
||||||
|
//need to encode the key
|
||||||
|
URLEncoder.encode(privateKey, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StringBuilder response = new StringBuilder();
|
||||||
|
return (NetworkUtils.sendGetRequest(url,null,response) == null) &&
|
||||||
|
response.toString().contains(LOGIN_SUCCESS_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int uploadImage(String[] placeId, String baseUrl, String privateKey, String username, String image) throws FailedVerificationException {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
Security.removeProvider("BC");
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
}
|
||||||
|
KeyPair kp = SecUtils.getKeyPair(ALGO_EC, privateKey, null);
|
||||||
|
String signed = username + ":opr-web";
|
||||||
|
|
||||||
|
JsonFormatter formatter = new JsonFormatter();
|
||||||
|
OPRImage oprImage = new GsonBuilder().create().fromJson(image, OPRImage.class);
|
||||||
|
OpOperation opOperation = new OpOperation();
|
||||||
|
opOperation.setType("opr.place");
|
||||||
|
List<Object> edits = new ArrayList<>();
|
||||||
|
Map<String, Object> edit = new TreeMap<>();
|
||||||
|
List<Object> imageResponseList = new ArrayList<>();
|
||||||
|
Map<String, Object> imageMap = new TreeMap<>();
|
||||||
|
imageMap.put("cid", oprImage.cid);
|
||||||
|
imageMap.put("hash", oprImage.hash);
|
||||||
|
imageMap.put("extension", oprImage.extension);
|
||||||
|
imageMap.put("type", oprImage.type);
|
||||||
|
imageResponseList.add(imageMap);
|
||||||
|
List<String> ids = new ArrayList<>(Arrays.asList(placeId));
|
||||||
|
Map<String, Object> change = new TreeMap<>();
|
||||||
|
Map<String, Object> images = new TreeMap<>();
|
||||||
|
Map<String, Object> outdoor = new TreeMap<>();
|
||||||
|
outdoor.put("outdoor", imageResponseList);
|
||||||
|
images.put("append", outdoor);
|
||||||
|
change.put("version", "increment");
|
||||||
|
change.put("images", images);
|
||||||
|
edit.put("id", ids);
|
||||||
|
edit.put("change", change);
|
||||||
|
edit.put("current", new Object());
|
||||||
|
edits.add(edit);
|
||||||
|
opOperation.putObjectValue(OpOperation.F_EDIT, edits);
|
||||||
|
opOperation.setSignedBy(signed);
|
||||||
|
String hash = JSON_MSG_TYPE + ":"
|
||||||
|
+ SecUtils.calculateHashWithAlgo(SecUtils.HASH_SHA256, null,
|
||||||
|
formatter.opToJsonNoHash(opOperation));
|
||||||
|
byte[] hashBytes = SecUtils.getHashBytes(hash);
|
||||||
|
String signature = signMessageWithKeyBase64(kp, hashBytes, SecUtils.SIG_ALGO_SHA1_EC, null);
|
||||||
|
opOperation.addOrSetStringValue("hash", hash);
|
||||||
|
opOperation.addOrSetStringValue("signature", signature);
|
||||||
|
TrafficStats.setThreadStatsTag(THREAD_ID);
|
||||||
|
String url = baseUrl + "api/auth/process-operation?addToQueue=true&dontSignByServer=false";
|
||||||
|
String json = formatter.opToJson(opOperation);
|
||||||
|
System.out.println("JSON: " + json);
|
||||||
|
HttpURLConnection connection;
|
||||||
|
try {
|
||||||
|
connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.setConnectTimeout(10000);
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
try {
|
||||||
|
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
|
||||||
|
wr.write(json.getBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
int rc = connection.getResponseCode();
|
||||||
|
if (rc != 200) {
|
||||||
|
log.error("ERROR HAPPENED");
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
|
||||||
|
String strCurrentLine;
|
||||||
|
while ((strCurrentLine = br.readLine()) != null) {
|
||||||
|
log.error(strCurrentLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OPRImage {
|
||||||
|
public String type;
|
||||||
|
public String hash;
|
||||||
|
public String cid;
|
||||||
|
public String extension;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,6 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.github.mikephil.charting.charts.LineChart;
|
|
||||||
|
|
||||||
import net.osmand.AndroidUtils;
|
import net.osmand.AndroidUtils;
|
||||||
import net.osmand.GPXUtilities.GPXFile;
|
import net.osmand.GPXUtilities.GPXFile;
|
||||||
import net.osmand.GPXUtilities.GPXTrackAnalysis;
|
import net.osmand.GPXUtilities.GPXTrackAnalysis;
|
||||||
|
@ -41,7 +39,6 @@ import net.osmand.data.QuadRect;
|
||||||
import net.osmand.data.TransportRoute;
|
import net.osmand.data.TransportRoute;
|
||||||
import net.osmand.data.TransportStop;
|
import net.osmand.data.TransportStop;
|
||||||
import net.osmand.plus.GeocodingLookupService;
|
import net.osmand.plus.GeocodingLookupService;
|
||||||
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
|
|
||||||
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
|
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
|
||||||
import net.osmand.plus.OsmAndFormatter;
|
import net.osmand.plus.OsmAndFormatter;
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
|
@ -82,8 +79,8 @@ import net.osmand.render.RenderingRulesStorage;
|
||||||
import net.osmand.router.RouteSegmentResult;
|
import net.osmand.router.RouteSegmentResult;
|
||||||
import net.osmand.router.RouteStatisticsHelper;
|
import net.osmand.router.RouteStatisticsHelper;
|
||||||
import net.osmand.router.RouteStatisticsHelper.RouteStatistics;
|
import net.osmand.router.RouteStatisticsHelper.RouteStatistics;
|
||||||
import net.osmand.router.TransportRouteResult;
|
|
||||||
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
|
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
|
||||||
|
import net.osmand.router.TransportRouteResult;
|
||||||
import net.osmand.util.Algorithms;
|
import net.osmand.util.Algorithms;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
@ -371,7 +368,9 @@ public class RouteDetailsFragment extends ContextMenuFragment
|
||||||
protected void calculateLayout(View view, boolean initLayout) {
|
protected void calculateLayout(View view, boolean initLayout) {
|
||||||
super.calculateLayout(view, initLayout);
|
super.calculateLayout(view, initLayout);
|
||||||
if (!initLayout && getCurrentMenuState() != MenuState.FULL_SCREEN) {
|
if (!initLayout && getCurrentMenuState() != MenuState.FULL_SCREEN) {
|
||||||
refreshMapCallback.refreshMap(false);
|
if (refreshMapCallback != null) {
|
||||||
|
refreshMapCallback.refreshMap(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
217
OsmAnd/src/org/openplacereviews/opendb/SecUtils.java
Normal file
217
OsmAnd/src/org/openplacereviews/opendb/SecUtils.java
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8
|
||||||
|
package org.openplacereviews.opendb;
|
||||||
|
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
|
||||||
|
import org.apache.commons.codec.DecoderException;
|
||||||
|
import org.apache.commons.codec.binary.Hex;
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.spec.EncodedKeySpec;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
//This class is a copy of SecUtils class from OpenDB project with changes for android platform
|
||||||
|
public class SecUtils {
|
||||||
|
public static final String SIG_ALGO_SHA1_EC = "SHA1withECDSA";
|
||||||
|
public static final String SIG_ALGO_NONE_EC = "NonewithECDSA";
|
||||||
|
|
||||||
|
public static final String SIG_ALGO_ECDSA = "ECDSA";
|
||||||
|
public static final String ALGO_EC = "EC";
|
||||||
|
|
||||||
|
public static final String DECODE_BASE64 = "base64";
|
||||||
|
public static final String HASH_SHA256 = "sha256";
|
||||||
|
public static final String HASH_SHA1 = "sha1";
|
||||||
|
|
||||||
|
public static final String JSON_MSG_TYPE = "json";
|
||||||
|
public static final String KEY_BASE64 = DECODE_BASE64;
|
||||||
|
|
||||||
|
|
||||||
|
public static EncodedKeySpec decodeKey(String key) {
|
||||||
|
if (key.startsWith(KEY_BASE64 + ":")) {
|
||||||
|
key = key.substring(KEY_BASE64.length() + 1);
|
||||||
|
int s = key.indexOf(':');
|
||||||
|
if (s == -1) {
|
||||||
|
throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify"));
|
||||||
|
}
|
||||||
|
//should use android.util.Base64 for android platform instead of Base64.getDecoder()
|
||||||
|
return getKeySpecByFormat(key.substring(0, s),
|
||||||
|
android.util.Base64.decode(key.substring(s + 1), Base64.DEFAULT));
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeKey(String algo, PublicKey pk) {
|
||||||
|
if (algo.equals(KEY_BASE64)) {
|
||||||
|
return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded());
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Algorithm is not supported: " + algo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeKey(String algo, PrivateKey pk) {
|
||||||
|
if (algo.equals(KEY_BASE64)) {
|
||||||
|
return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded());
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Algorithm is not supported: " + algo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EncodedKeySpec getKeySpecByFormat(String format, byte[] data) {
|
||||||
|
switch (format) {
|
||||||
|
case "PKCS#8":
|
||||||
|
return new PKCS8EncodedKeySpec(data);
|
||||||
|
case "X.509":
|
||||||
|
return new X509EncodedKeySpec(data);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeBase64(byte[] data) {
|
||||||
|
//should use android.util.Base64 for android platform instead of Base64.getDecoder()
|
||||||
|
return new String(android.util.Base64.decode(data, android.util.Base64.DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean validateKeyPair(String algo, PrivateKey privateKey, PublicKey publicKey)
|
||||||
|
throws FailedVerificationException {
|
||||||
|
if (!algo.equals(ALGO_EC)) {
|
||||||
|
throw new FailedVerificationException("Algorithm is not supported: " + algo);
|
||||||
|
}
|
||||||
|
// create a challenge
|
||||||
|
byte[] challenge = new byte[512];
|
||||||
|
ThreadLocalRandom.current().nextBytes(challenge);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// sign using the private key
|
||||||
|
Signature sig = Signature.getInstance(SIG_ALGO_SHA1_EC);
|
||||||
|
sig.initSign(privateKey);
|
||||||
|
sig.update(challenge);
|
||||||
|
byte[] signature = sig.sign();
|
||||||
|
|
||||||
|
// verify signature using the public key
|
||||||
|
sig.initVerify(publicKey);
|
||||||
|
sig.update(challenge);
|
||||||
|
|
||||||
|
boolean keyPairMatches = sig.verify(signature);
|
||||||
|
return keyPairMatches;
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair getKeyPair(String algo, String prKey, String pbKey) throws FailedVerificationException {
|
||||||
|
try {
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(algo);
|
||||||
|
PublicKey pb = null;
|
||||||
|
PrivateKey pr = null;
|
||||||
|
if (pbKey != null) {
|
||||||
|
pb = keyFactory.generatePublic(decodeKey(pbKey));
|
||||||
|
}
|
||||||
|
if (prKey != null) {
|
||||||
|
pr = keyFactory.generatePrivate(decodeKey(prKey));
|
||||||
|
}
|
||||||
|
return new KeyPair(pb, pr);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String signMessageWithKeyBase64(KeyPair keyPair, byte[] msg, String signAlgo, ByteArrayOutputStream out) {
|
||||||
|
byte[] sigBytes;
|
||||||
|
try {
|
||||||
|
sigBytes = signMessageWithKey(keyPair, msg, signAlgo);
|
||||||
|
} catch (FailedVerificationException e) {
|
||||||
|
throw new IllegalStateException("Cannot get bytes");
|
||||||
|
}
|
||||||
|
if (out != null) {
|
||||||
|
try {
|
||||||
|
out.write(sigBytes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String signature = Base64.encodeToString(sigBytes, Base64.DEFAULT).replace("\n", "");
|
||||||
|
return signAlgo + ":" + DECODE_BASE64 + ":" + signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] signMessageWithKey(KeyPair keyPair, byte[] msg, String signAlgo) throws FailedVerificationException {
|
||||||
|
try {
|
||||||
|
//use BouncyCastle on android platform in order to achieve consistency between platforms
|
||||||
|
Signature sig = Signature.getInstance(getInternalSigAlgo(signAlgo), "BC");
|
||||||
|
sig.initSign(keyPair.getPrivate());
|
||||||
|
sig.update(msg);
|
||||||
|
byte[] signatureBytes = sig.sign();
|
||||||
|
return signatureBytes;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
throw new FailedVerificationException(e);
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getInternalSigAlgo(String sigAlgo) {
|
||||||
|
return sigAlgo.equals(SIG_ALGO_ECDSA) ? SIG_ALGO_NONE_EC : sigAlgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] calculateHash(String algo, byte[] b1, byte[] b2) {
|
||||||
|
byte[] m = mergeTwoArrays(b1, b2);
|
||||||
|
if (algo.equals(HASH_SHA256)) {
|
||||||
|
return DigestUtils.sha256(m);
|
||||||
|
} else if (algo.equals(HASH_SHA1)) {
|
||||||
|
return DigestUtils.sha1(m);
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] mergeTwoArrays(byte[] b1, byte[] b2) {
|
||||||
|
byte[] m = b1 == null ? b2 : b1;
|
||||||
|
if (b2 != null && b1 != null) {
|
||||||
|
m = new byte[b1.length + b2.length];
|
||||||
|
System.arraycopy(b1, 0, m, 0, b1.length);
|
||||||
|
System.arraycopy(b2, 0, m, b1.length, b2.length);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String calculateHashWithAlgo(String algo, String salt, String msg) {
|
||||||
|
try {
|
||||||
|
//use Hex.encodeHex for android platform instead of Hex.encodeHexString
|
||||||
|
char[] hex = Hex.encodeHex(calculateHash(algo, salt == null ? null : salt.getBytes("UTF-8"),
|
||||||
|
msg == null ? null : msg.getBytes("UTF-8")));
|
||||||
|
|
||||||
|
return algo + ":" + new String(hex);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getHashBytes(String msg) {
|
||||||
|
if (msg == null || msg.length() == 0) {
|
||||||
|
// special case for empty hash
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
int i = msg.lastIndexOf(':');
|
||||||
|
String s = i >= 0 ? msg.substring(i + 1) : msg;
|
||||||
|
try {
|
||||||
|
return Hex.decodeHex(s.toCharArray());
|
||||||
|
} catch (DecoderException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
537
OsmAnd/src/org/openplacereviews/opendb/ops/OpObject.java
Normal file
537
OsmAnd/src/org/openplacereviews/opendb/ops/OpObject.java
Normal file
|
@ -0,0 +1,537 @@
|
||||||
|
//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8
|
||||||
|
package org.openplacereviews.opendb.ops;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import org.openplacereviews.opendb.util.JsonObjectUtils;
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed unused imports
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class OpObject {
|
||||||
|
|
||||||
|
public static final String F_NAME = "name";
|
||||||
|
public static final String F_ID = "id";
|
||||||
|
public static final String F_COMMENT = "comment";
|
||||||
|
public static final String TYPE_OP = "sys.op";
|
||||||
|
public static final String TYPE_BLOCK = "sys.block";
|
||||||
|
public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||||
|
// transient info about validation timing etc
|
||||||
|
public static final String F_EVAL = "eval";
|
||||||
|
public static final String F_VALIDATION = "validation";
|
||||||
|
public static final String F_TIMESTAMP_ADDED = "timestamp";
|
||||||
|
public static final String F_PARENT_TYPE = "parentType";
|
||||||
|
public static final String F_PARENT_HASH = "parentHash";
|
||||||
|
public static final String F_CHANGE = "change";
|
||||||
|
public static final String F_CURRENT = "current";
|
||||||
|
// voting
|
||||||
|
public static final String F_OP = "op";
|
||||||
|
public static final String F_STATE = "state";
|
||||||
|
public static final String F_OPEN = "open";
|
||||||
|
public static final String F_FINAL = "final";
|
||||||
|
public static final String F_VOTE = "vote";
|
||||||
|
public static final String F_VOTES = "votes";
|
||||||
|
public static final String F_SUBMITTED_OP_HASH = "submittedOpHash";
|
||||||
|
public static final String F_USER = "user";
|
||||||
|
|
||||||
|
public static final OpObject NULL = new OpObject(true);
|
||||||
|
|
||||||
|
public static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
static {
|
||||||
|
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Object> fields = new TreeMap<>();
|
||||||
|
protected transient Map<String, Object> cacheFields;
|
||||||
|
protected boolean isImmutable;
|
||||||
|
|
||||||
|
protected transient String parentType;
|
||||||
|
protected transient String parentHash;
|
||||||
|
protected transient boolean deleted;
|
||||||
|
|
||||||
|
|
||||||
|
public OpObject() {}
|
||||||
|
|
||||||
|
public OpObject(boolean deleted) {
|
||||||
|
this.deleted = deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpObject(OpObject cp) {
|
||||||
|
this(cp, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpObject(OpObject cp, boolean copyCacheFields) {
|
||||||
|
createOpObjectCopy(cp, copyCacheFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private OpObject createOpObjectCopy(OpObject opObject, boolean copyCacheFields) {
|
||||||
|
this.parentType = opObject.parentType;
|
||||||
|
this.parentHash = opObject.parentHash;
|
||||||
|
this.deleted = opObject.deleted;
|
||||||
|
this.fields = (Map<String, Object>) copyingObjects(opObject.fields, copyCacheFields);
|
||||||
|
if (opObject.cacheFields != null && copyCacheFields) {
|
||||||
|
this.cacheFields = (Map<String, Object>) copyingObjects(opObject.cacheFields, copyCacheFields);
|
||||||
|
}
|
||||||
|
this.isImmutable = false;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Object copyingObjects(Object object, boolean copyCacheFields) {
|
||||||
|
if (object instanceof Number) {
|
||||||
|
return (Number) object;
|
||||||
|
} else if (object instanceof String) {
|
||||||
|
return (String) object;
|
||||||
|
} else if (object instanceof Boolean) {
|
||||||
|
return (Boolean) object;
|
||||||
|
} else if (object instanceof List) {
|
||||||
|
List<Object> copy = new ArrayList<>();
|
||||||
|
List<Object> list = (List<Object>) object;
|
||||||
|
for (Object o : list) {
|
||||||
|
copy.add(copyingObjects(o, copyCacheFields));
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
} else if (object instanceof Map) {
|
||||||
|
Map<Object, Object> copy = new LinkedHashMap<>();
|
||||||
|
Map<Object, Object> map = (Map<Object, Object>) object;
|
||||||
|
for (Object o : map.keySet()) {
|
||||||
|
copy.put(o, copyingObjects(map.get(o), copyCacheFields));
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed instanceOf OpExprEvaluator
|
||||||
|
// OSMAND ANDROID CHANGE END:
|
||||||
|
else if (object instanceof OpObject) {
|
||||||
|
return new OpObject((OpObject) object);
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Type of object is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentOp(OpOperation op) {
|
||||||
|
setParentOp(op.type, op.getRawHash());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentOp(String parentType, String parentHash) {
|
||||||
|
this.parentType = parentType;
|
||||||
|
this.parentHash = parentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParentHash() {
|
||||||
|
return parentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParentType() {
|
||||||
|
return parentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getId() {
|
||||||
|
return getStringList(F_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
addOrSetStringValue(F_ID, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isImmutable() {
|
||||||
|
return isImmutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpObject makeImmutable() {
|
||||||
|
isImmutable = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getFieldByExpr(String field) {
|
||||||
|
if (field.contains(".") || field.contains("[") || field.contains("]")) {
|
||||||
|
return JsonObjectUtils.getField(this.fields, generateFieldSequence(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generateFieldSequence("a") - [a]
|
||||||
|
* generateFieldSequence("a.b") - [a, b]
|
||||||
|
* generateFieldSequence("a.b.c.de") - [a, b, c, de]
|
||||||
|
* generateFieldSequence("a.bwerq.c") - [a, bwerq, c]
|
||||||
|
* generateFieldSequence("a.bwerq...c") - [a, bwerq, c]
|
||||||
|
* generateFieldSequence("a.bwereq..c..") - [a, bwerq, c]
|
||||||
|
* generateFieldSequence("a.{b}") - [a, b]
|
||||||
|
* generateFieldSequence("a.{b.c.de}") - [a, b.c.de]
|
||||||
|
* generateFieldSequence("a.{b.c.de}") - [a, b.c.de]
|
||||||
|
* generateFieldSequence("a.{b{}}") - [a, b{}]
|
||||||
|
* generateFieldSequence("a.{b{}d.q}") - [a, b{}d.q]
|
||||||
|
*/
|
||||||
|
private static List<String> generateFieldSequence(String field) {
|
||||||
|
int STATE_OPEN_BRACE = 1;
|
||||||
|
int STATE_OPEN = 0;
|
||||||
|
int state = STATE_OPEN;
|
||||||
|
int start = 0;
|
||||||
|
List<String> l = new ArrayList<String>();
|
||||||
|
for(int i = 0; i < field.length(); i++) {
|
||||||
|
boolean split = false;
|
||||||
|
if (i == field.length() - 1) {
|
||||||
|
if (state == STATE_OPEN_BRACE) {
|
||||||
|
if(field.charAt(i) == '}') {
|
||||||
|
split = true;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Illegal field expression: " + field);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(field.charAt(i) != '.') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
split = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (field.charAt(i) == '.' && state == STATE_OPEN) {
|
||||||
|
split = true;
|
||||||
|
} else if (field.charAt(i) == '}' && field.charAt(i + 1) == '.' && state == STATE_OPEN_BRACE) {
|
||||||
|
split = true;
|
||||||
|
} else if (field.charAt(i) == '{' && state == STATE_OPEN) {
|
||||||
|
if(start != i) {
|
||||||
|
throw new IllegalArgumentException("Illegal field expression (wrap {} is necessary): " + field);
|
||||||
|
}
|
||||||
|
state = STATE_OPEN_BRACE;
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(split) {
|
||||||
|
if (i != start) {
|
||||||
|
l.add(field.substring(start, i));
|
||||||
|
}
|
||||||
|
start = i + 1;
|
||||||
|
state = STATE_OPEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFieldByExpr(String field, Object object) {
|
||||||
|
if (field.contains(".") || field.contains("[") || field.contains("]")) {
|
||||||
|
List<String> fieldSequence = generateFieldSequence(field);
|
||||||
|
if (object == null) {
|
||||||
|
JsonObjectUtils.deleteField(this.fields, fieldSequence);
|
||||||
|
} else {
|
||||||
|
JsonObjectUtils.setField(this.fields, fieldSequence, object);
|
||||||
|
}
|
||||||
|
} else if (object == null) {
|
||||||
|
fields.remove(field);
|
||||||
|
} else {
|
||||||
|
fields.put(field, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Object getCacheObject(String f) {
|
||||||
|
if(cacheFields == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return cacheFields.get(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putCacheObject(String f, Object o) {
|
||||||
|
if (isImmutable()) {
|
||||||
|
if (cacheFields == null) {
|
||||||
|
cacheFields = new ConcurrentHashMap<String, Object>();
|
||||||
|
}
|
||||||
|
cacheFields.put(f, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id, String id2) {
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
list.add(id);
|
||||||
|
list.add(id2);
|
||||||
|
putObjectValue(F_ID, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return getStringValue(F_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return getStringValue(F_COMMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getRawOtherFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, String> getStringMap(String field) {
|
||||||
|
return (Map<String, String>) fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, List<String>> getMapStringList(String field) {
|
||||||
|
return (Map<String, List<String>>) fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<Map<String, String>> getListStringMap(String field) {
|
||||||
|
return (List<Map<String, String>>) fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<Map<String, Object>> getListStringObjMap(String field) {
|
||||||
|
return (List<Map<String, Object>>) fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, Object> getStringObjMap(String field) {
|
||||||
|
return (Map<String, Object>) fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T getField(T def, String... fields) {
|
||||||
|
Map<String, Object> p = this.fields;
|
||||||
|
for(int i = 0; i < fields.length - 1 ; i++) {
|
||||||
|
p = (Map<String, Object>) p.get(fields[i]);
|
||||||
|
if(p == null) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
T res = (T) p.get(fields[fields.length - 1]);
|
||||||
|
if(res == null) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<List<String>, Object> getStringListObjMap(String field) {
|
||||||
|
return (Map<List<String>, Object>) fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public long getDate(String field) {
|
||||||
|
String date = getStringValue(field);
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed check OUtils.isEmpty(date)
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
try {
|
||||||
|
return dateFormat.parse(date).getTime();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setDate(String field, long time) {
|
||||||
|
putStringValue(field, dateFormat.format(new Date(time)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Number getNumberValue(String field) {
|
||||||
|
return (Number) fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntValue(String key, int def) {
|
||||||
|
Number o = getNumberValue(key);
|
||||||
|
return o == null ? def : o.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLongValue(String key, long def) {
|
||||||
|
Number o = getNumberValue(key);
|
||||||
|
return o == null ? def : o.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringValue(String field) {
|
||||||
|
Object o = fields.get(field);
|
||||||
|
if (o instanceof String || o == null) {
|
||||||
|
return (String) o;
|
||||||
|
}
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<String> getStringList(String field) {
|
||||||
|
// cast to list if it is single value
|
||||||
|
Object o = fields.get(field);
|
||||||
|
if(o == null || o.toString().isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
if(o instanceof String) {
|
||||||
|
return Collections.singletonList(o.toString());
|
||||||
|
}
|
||||||
|
return (List<String>) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getObjectValue(String field) {
|
||||||
|
return fields.get(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putStringValue(String key, String value) {
|
||||||
|
checkNotImmutable();
|
||||||
|
if(value == null) {
|
||||||
|
fields.remove(key);
|
||||||
|
} else {
|
||||||
|
fields.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operates as a single value if cardinality is less than 1
|
||||||
|
* or as a list of values if it stores > 1 value
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void addOrSetStringValue(String key, String value) {
|
||||||
|
checkNotImmutable();
|
||||||
|
Object o = fields.get(key);
|
||||||
|
if(o == null) {
|
||||||
|
fields.put(key, value);
|
||||||
|
} else if(o instanceof List) {
|
||||||
|
((List<String>) o).add(value);
|
||||||
|
} else {
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
list.add(o.toString());
|
||||||
|
list.add(value);
|
||||||
|
fields.put(key, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, Object> getChangedEditFields() {
|
||||||
|
return (Map<String, Object>) fields.get(F_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, Object> getCurrentEditFields() {
|
||||||
|
return (Map<String, Object>) fields.get(F_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putObjectValue(String key, Object value) {
|
||||||
|
checkNotImmutable();
|
||||||
|
if(value == null) {
|
||||||
|
fields.remove(key);
|
||||||
|
} else {
|
||||||
|
fields.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkNotImmutable() {
|
||||||
|
if(isImmutable) {
|
||||||
|
throw new IllegalStateException("Object is immutable");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkImmutable() {
|
||||||
|
if(!isImmutable) {
|
||||||
|
throw new IllegalStateException("Object is mutable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object remove(String key) {
|
||||||
|
checkNotImmutable();
|
||||||
|
return fields.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMixedFieldsAndCacheMap() {
|
||||||
|
TreeMap<String, Object> mp = new TreeMap<>(fields);
|
||||||
|
if(cacheFields != null || parentType != null || parentHash != null) {
|
||||||
|
TreeMap<String, Object> eval = new TreeMap<String, Object>();
|
||||||
|
|
||||||
|
if(parentType != null) {
|
||||||
|
eval.put(F_PARENT_TYPE, parentType);
|
||||||
|
}
|
||||||
|
if(parentHash != null) {
|
||||||
|
eval.put(F_PARENT_HASH, parentHash);
|
||||||
|
}
|
||||||
|
if (cacheFields != null) {
|
||||||
|
Iterator<Entry<String, Object>> it = cacheFields.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Entry<String, Object> e = it.next();
|
||||||
|
Object v = e.getValue();
|
||||||
|
if (v instanceof Map || v instanceof String || v instanceof Number) {
|
||||||
|
eval.put(e.getKey(), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(eval.size() > 0) {
|
||||||
|
mp.put(F_EVAL, eval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((fields == null) ? 0 : fields.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "[" + fields + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
OpObject other = (OpObject) obj;
|
||||||
|
if (fields == null) {
|
||||||
|
if (other.fields != null)
|
||||||
|
return false;
|
||||||
|
} else if (!fields.equals(other.fields))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OpObjectAdapter implements JsonDeserializer<OpObject>,
|
||||||
|
JsonSerializer<OpObject> {
|
||||||
|
|
||||||
|
private boolean fullOutput;
|
||||||
|
|
||||||
|
public OpObjectAdapter(boolean fullOutput) {
|
||||||
|
this.fullOutput = fullOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||||
|
throws JsonParseException {
|
||||||
|
OpObject bn = new OpObject();
|
||||||
|
bn.fields = context.deserialize(json, TreeMap.class);
|
||||||
|
// remove cache
|
||||||
|
bn.fields.remove(F_EVAL);
|
||||||
|
return bn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(OpObject src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return context.serialize(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
289
OsmAnd/src/org/openplacereviews/opendb/ops/OpOperation.java
Normal file
289
OsmAnd/src/org/openplacereviews/opendb/ops/OpOperation.java
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8
|
||||||
|
package org.openplacereviews.opendb.ops;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.*;
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed dependency OUtils
|
||||||
|
// OSMAND ANDROID CHANGE END:
|
||||||
|
public class OpOperation extends OpObject {
|
||||||
|
|
||||||
|
public static final String F_TYPE = "type";
|
||||||
|
public static final String F_SIGNED_BY = "signed_by";
|
||||||
|
public static final String F_HASH = "hash";
|
||||||
|
|
||||||
|
public static final String F_SIGNATURE = "signature";
|
||||||
|
|
||||||
|
public static final String F_REF = "ref";
|
||||||
|
public static final String F_CREATE = "create";
|
||||||
|
public static final String F_DELETE = "delete";
|
||||||
|
public static final String F_EDIT = "edit";
|
||||||
|
|
||||||
|
public static final String F_NAME = "name";
|
||||||
|
public static final String F_COMMENT = "comment";
|
||||||
|
|
||||||
|
private List<OpObject> createdObjects = new LinkedList<OpObject>();
|
||||||
|
private List<OpObject> editedObjects = new LinkedList<OpObject>();
|
||||||
|
protected String type;
|
||||||
|
|
||||||
|
public OpOperation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpOperation(OpOperation cp, boolean copyCacheFields) {
|
||||||
|
super(cp, copyCacheFields);
|
||||||
|
this.type = cp.type;
|
||||||
|
for(OpObject o : cp.createdObjects) {
|
||||||
|
this.createdObjects.add(new OpObject(o, copyCacheFields));
|
||||||
|
}
|
||||||
|
for(OpObject o : cp.editedObjects) {
|
||||||
|
this.editedObjects.add(new OpObject(o, copyCacheFields));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOperationType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String name) {
|
||||||
|
checkNotImmutable();
|
||||||
|
type = name;
|
||||||
|
updateObjectsRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateObjectsRef() {
|
||||||
|
for(OpObject o : createdObjects) {
|
||||||
|
o.setParentOp(this);
|
||||||
|
}
|
||||||
|
for(OpObject o : editedObjects) {
|
||||||
|
o.setParentOp(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpOperation makeImmutable() {
|
||||||
|
isImmutable = true;
|
||||||
|
for(OpObject o : createdObjects) {
|
||||||
|
o.makeImmutable();
|
||||||
|
}
|
||||||
|
for(OpObject o : editedObjects) {
|
||||||
|
o.makeImmutable();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignedBy(String value) {
|
||||||
|
putStringValue(F_SIGNED_BY, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addOtherSignedBy(String value) {
|
||||||
|
super.addOrSetStringValue(F_SIGNED_BY, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSignedBy() {
|
||||||
|
return getStringList(F_SIGNED_BY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return getStringValue(F_HASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRawHash() {
|
||||||
|
String rw = getStringValue(F_HASH);
|
||||||
|
// drop algorithm and everything else
|
||||||
|
if(rw != null) {
|
||||||
|
rw = rw.substring(rw.lastIndexOf(':') + 1);
|
||||||
|
}
|
||||||
|
return rw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSignatureList() {
|
||||||
|
return getStringList(F_SIGNATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getRef() {
|
||||||
|
return getMapStringList(F_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<List<String>> getDeleted() {
|
||||||
|
List<List<String>> l = (List<List<String>>) fields.get(F_DELETE);
|
||||||
|
if(l == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasDeleted() {
|
||||||
|
return getDeleted().size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDeleted(List<String> id) {
|
||||||
|
if(!fields.containsKey(F_DELETE)) {
|
||||||
|
ArrayList<List<String>> lst = new ArrayList<>();
|
||||||
|
lst.add(id);
|
||||||
|
putObjectValue(F_DELETE, lst);
|
||||||
|
} else {
|
||||||
|
getDeleted().add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OpObject> getCreated() {
|
||||||
|
return createdObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCreated(OpObject o) {
|
||||||
|
checkNotImmutable();
|
||||||
|
createdObjects.add(o);
|
||||||
|
if(type != null) {
|
||||||
|
o.setParentOp(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCreated() {
|
||||||
|
return createdObjects.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEdited(OpObject o) {
|
||||||
|
checkNotImmutable();
|
||||||
|
editedObjects.add(o);
|
||||||
|
if (type != null) {
|
||||||
|
o.setParentOp(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OpObject> getEdited() {
|
||||||
|
return editedObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasEdited() {
|
||||||
|
return editedObjects.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return getStringValue(F_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return getStringValue(F_COMMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = prime * result + ((createdObjects == null) ? 0 : createdObjects.hashCode());
|
||||||
|
result = prime * result + ((editedObjects == null) ? 0 : editedObjects.hashCode());
|
||||||
|
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (!super.equals(obj))
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
OpOperation other = (OpOperation) obj;
|
||||||
|
if (createdObjects == null) {
|
||||||
|
if (other.createdObjects != null)
|
||||||
|
return false;
|
||||||
|
} else if (!createdObjects.equals(other.createdObjects))
|
||||||
|
return false;
|
||||||
|
if (editedObjects == null) {
|
||||||
|
if (other.editedObjects != null)
|
||||||
|
return false;
|
||||||
|
} else if (!editedObjects.equals(other.editedObjects))
|
||||||
|
return false;
|
||||||
|
if (type == null) {
|
||||||
|
if (other.type != null)
|
||||||
|
return false;
|
||||||
|
} else if (!type.equals(other.type))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed unused classes and methods
|
||||||
|
// public static OpObjectDiffBuilder createDiffOperation(OpObject o)
|
||||||
|
// private static Object diffSet(Object vl)
|
||||||
|
// public static class OpObjectDiffBuilder{}
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
|
||||||
|
public static class OpOperationBeanAdapter implements JsonDeserializer<OpOperation>,
|
||||||
|
JsonSerializer<OpOperation> {
|
||||||
|
|
||||||
|
// plain serialization to calculate hash
|
||||||
|
private boolean excludeHashAndSignature;
|
||||||
|
private boolean fullOutput;
|
||||||
|
|
||||||
|
public OpOperationBeanAdapter(boolean fullOutput, boolean excludeHashAndSignature) {
|
||||||
|
this.excludeHashAndSignature = excludeHashAndSignature;
|
||||||
|
this.fullOutput = fullOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpOperationBeanAdapter(boolean fullOutput) {
|
||||||
|
this.fullOutput = fullOutput;
|
||||||
|
this.excludeHashAndSignature = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||||
|
throws JsonParseException {
|
||||||
|
JsonObject jsonObj = json.getAsJsonObject();
|
||||||
|
OpOperation op = new OpOperation();
|
||||||
|
JsonElement tp = jsonObj.remove(F_TYPE);
|
||||||
|
if(tp != null) {
|
||||||
|
String opType = tp.getAsString();
|
||||||
|
op.type = opType;
|
||||||
|
} else {
|
||||||
|
op.type = "";
|
||||||
|
}
|
||||||
|
JsonElement createdObjs = jsonObj.remove(F_CREATE);
|
||||||
|
if(createdObjs != null) {
|
||||||
|
JsonArray ar = createdObjs.getAsJsonArray();
|
||||||
|
for(int i = 0; i < ar.size(); i++) {
|
||||||
|
op.addCreated((OpObject) context.deserialize(ar.get(i), OpObject.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonElement editedObjs = jsonObj.remove(F_EDIT);
|
||||||
|
if (editedObjs != null) {
|
||||||
|
for (JsonElement editElem : editedObjs.getAsJsonArray()) {
|
||||||
|
op.addEdited((OpObject) context.deserialize(editElem, OpObject.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonObj.remove(F_EVAL);
|
||||||
|
op.fields = context.deserialize(jsonObj, TreeMap.class);
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(OpOperation src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
TreeMap<String, Object> tm = new TreeMap<>(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields);
|
||||||
|
if(excludeHashAndSignature) {
|
||||||
|
tm.remove(F_SIGNATURE);
|
||||||
|
tm.remove(F_HASH);
|
||||||
|
}
|
||||||
|
tm.put(F_TYPE, src.type);
|
||||||
|
|
||||||
|
if (src.hasEdited()) {
|
||||||
|
tm.put(F_EDIT, context.serialize(src.editedObjects));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(src.hasCreated()) {
|
||||||
|
tm.put(F_CREATE, context.serialize(src.createdObjects));
|
||||||
|
}
|
||||||
|
return context.serialize(tm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
157
OsmAnd/src/org/openplacereviews/opendb/util/JsonFormatter.java
Normal file
157
OsmAnd/src/org/openplacereviews/opendb/util/JsonFormatter.java
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8
|
||||||
|
package org.openplacereviews.opendb.util;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed dependency org.openplacereviews.opendb.ops.OpBlock;
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
import org.openplacereviews.opendb.ops.OpObject;
|
||||||
|
import org.openplacereviews.opendb.ops.OpOperation;
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed dependency org.springframework.stereotype.Component;
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed annotation @Component
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
public class JsonFormatter {
|
||||||
|
|
||||||
|
private Gson gson;
|
||||||
|
|
||||||
|
private Gson gsonOperationHash;
|
||||||
|
|
||||||
|
private Gson gsonFullOutput;
|
||||||
|
|
||||||
|
public JsonFormatter() {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
builder.disableHtmlEscaping();
|
||||||
|
builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false));
|
||||||
|
builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false));
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed OpBlock.class TypeAdapter
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix());
|
||||||
|
gson = builder.create();
|
||||||
|
|
||||||
|
builder = new GsonBuilder();
|
||||||
|
builder.disableHtmlEscaping();
|
||||||
|
builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false, true));
|
||||||
|
builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false));
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed OpBlock.class TypeAdapter
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix());
|
||||||
|
gsonOperationHash = builder.create();
|
||||||
|
|
||||||
|
builder = new GsonBuilder();
|
||||||
|
builder.disableHtmlEscaping();
|
||||||
|
builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(true));
|
||||||
|
builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(true));
|
||||||
|
builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix());
|
||||||
|
gsonFullOutput = builder.create();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer<TreeMap<String, Object>> {
|
||||||
|
|
||||||
|
@Override @SuppressWarnings("unchecked")
|
||||||
|
public TreeMap<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
return (TreeMap<String, Object>) read(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object read(JsonElement in) {
|
||||||
|
|
||||||
|
if(in.isJsonArray()){
|
||||||
|
List<Object> list = new ArrayList<Object>();
|
||||||
|
JsonArray arr = in.getAsJsonArray();
|
||||||
|
for (JsonElement anArr : arr) {
|
||||||
|
list.add(read(anArr));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}else if(in.isJsonObject()){
|
||||||
|
Map<String, Object> map = new TreeMap<String, Object>();
|
||||||
|
JsonObject obj = in.getAsJsonObject();
|
||||||
|
Set<Map.Entry<String, JsonElement>> entitySet = obj.entrySet();
|
||||||
|
for(Map.Entry<String, JsonElement> entry: entitySet){
|
||||||
|
map.put(entry.getKey(), read(entry.getValue()));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}else if(in.isJsonPrimitive()){
|
||||||
|
JsonPrimitive prim = in.getAsJsonPrimitive();
|
||||||
|
if(prim.isBoolean()){
|
||||||
|
return prim.getAsBoolean();
|
||||||
|
}else if(prim.isString()){
|
||||||
|
return prim.getAsString();
|
||||||
|
}else if(prim.isNumber()){
|
||||||
|
Number num = prim.getAsNumber();
|
||||||
|
// here you can handle double int/long values
|
||||||
|
// and return any type you want
|
||||||
|
// this solution will transform 3.0 float to long values
|
||||||
|
if(Math.ceil(num.doubleValue()) == num.longValue() && (!num.toString().contains(".") || num.toString().split("\\.")[1].length() <= 1))
|
||||||
|
return num.longValue();
|
||||||
|
else {
|
||||||
|
return num.doubleValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// operations to parse / format related
|
||||||
|
public OpOperation parseOperation(String opJson) {
|
||||||
|
return gson.fromJson(opJson, OpOperation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpObject parseObject(String opJson) {
|
||||||
|
return gson.fromJson(opJson, OpObject.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSMAND ANDROID CHANGE BEGIN:
|
||||||
|
// removed unused methods
|
||||||
|
// public OpBlock parseBlock(String opJson)
|
||||||
|
// public String toJson(OpBlock bl)
|
||||||
|
// OSMAND ANDROID CHANGE END
|
||||||
|
|
||||||
|
public JsonElement toJsonElement(Object o) {
|
||||||
|
return gson.toJsonTree(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public TreeMap<String, Object> fromJsonToTreeMap(String json) {
|
||||||
|
return gson.fromJson(json, TreeMap.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException {
|
||||||
|
return gson.fromJson(json, classOfT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fromJson(Reader json, Type typeOfT) throws JsonSyntaxException {
|
||||||
|
return gson.fromJson(json, typeOfT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fullObjectToJson(Object o) {
|
||||||
|
return gsonFullOutput.toJson(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String opToJsonNoHash(OpOperation op) {
|
||||||
|
return gsonOperationHash.toJson(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String opToJson(OpOperation op) {
|
||||||
|
return gson.toJson(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String objToJson(OpObject op) {
|
||||||
|
return gson.toJson(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
276
OsmAnd/src/org/openplacereviews/opendb/util/JsonObjectUtils.java
Normal file
276
OsmAnd/src/org/openplacereviews/opendb/util/JsonObjectUtils.java
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8
|
||||||
|
package org.openplacereviews.opendb.util;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class uses for work with Json Object represent as Map.
|
||||||
|
*/
|
||||||
|
public class JsonObjectUtils {
|
||||||
|
|
||||||
|
|
||||||
|
private static final int GET_OPERATION = 0;
|
||||||
|
private static final int SET_OPERATION = 1;
|
||||||
|
private static final int DELETE_OPERATION = 2;
|
||||||
|
protected static final Log LOGGER = LogFactory.getLog(JsonObjectUtils.class);
|
||||||
|
|
||||||
|
private static class OperationAccess {
|
||||||
|
private final int operation;
|
||||||
|
private final Object value;
|
||||||
|
|
||||||
|
private OperationAccess(int op, Object v) {
|
||||||
|
this.operation = op;
|
||||||
|
this.value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve value from jsonMap by field sequence.
|
||||||
|
* @param jsonMap source json object deserialized in map
|
||||||
|
* @param fieldSequence Sequence to field value.
|
||||||
|
* Example: person.car.number have to be ["person", "car[2]", "number"]
|
||||||
|
* @return Field value
|
||||||
|
*/
|
||||||
|
public static Object getField(Map<String, Object> jsonMap, String[] fieldSequence) {
|
||||||
|
return accessField(jsonMap, fieldSequence, new OperationAccess(GET_OPERATION, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value to json field (path to field presented as sequence of string)
|
||||||
|
*
|
||||||
|
* @param jsonMap source json object deserialized in map
|
||||||
|
* @param fieldSequence Sequence to field value.
|
||||||
|
* * Example: person.car.number have to be ["person", "car[2]", "number"]
|
||||||
|
* @param field field value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Object setField(Map<String, Object> jsonMap, List<String> fieldSequence, Object field) {
|
||||||
|
return setField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value to json field (path to field presented as sequence of string)
|
||||||
|
*
|
||||||
|
* @param jsonObject source json object deserialized in map
|
||||||
|
* @param fieldSequence Sequence to field value.
|
||||||
|
* * Example: person.car.number have to be ["person", "car[2]", "number"]
|
||||||
|
* @param field field value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Object setField(Map<String, Object> jsonObject, String[] fieldSequence, Object field) {
|
||||||
|
return accessField(jsonObject, fieldSequence, new OperationAccess(SET_OPERATION, field));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve value from jsonMap by field sequence.
|
||||||
|
*
|
||||||
|
* @param jsonObject source json object deserialized in map
|
||||||
|
* @param fieldSequence Sequence to field value.
|
||||||
|
* Example: person.car.number have to be ["person", "car[2]", "number"]
|
||||||
|
* @return Field value
|
||||||
|
*/
|
||||||
|
public static Object getField(Map<String, Object> jsonObject, List<String> fieldSequence) {
|
||||||
|
return getField(jsonObject, fieldSequence.toArray(new String[fieldSequence.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete field value from json Map (field path presented as sequence of string)
|
||||||
|
*
|
||||||
|
* @param jsonMap source json object deserialized in map
|
||||||
|
* @param fieldSequence Sequence to field value.
|
||||||
|
* Example: person.car.number have to be ["person", "car[2]", "number"]
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Object deleteField(Map<String, Object> jsonMap, List<String> fieldSequence) {
|
||||||
|
return accessField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), new OperationAccess(DELETE_OPERATION, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Object accessField(Map<String, Object> jsonObject, String[] fieldSequence, OperationAccess op) {
|
||||||
|
if (fieldSequence == null || fieldSequence.length == 0) {
|
||||||
|
throw new IllegalArgumentException("Field sequence is empty. Set value to root not possible.");
|
||||||
|
}
|
||||||
|
String fieldName = null;
|
||||||
|
Map<String, Object> jsonObjLocal = jsonObject;
|
||||||
|
List<Object> jsonListLocal = null;
|
||||||
|
int indexToAccess = -1;
|
||||||
|
for(int i = 0; i < fieldSequence.length; i++) {
|
||||||
|
boolean last = i == fieldSequence.length - 1;
|
||||||
|
fieldName = fieldSequence[i];
|
||||||
|
int indOpArray = -1;
|
||||||
|
for(int ic = 0; ic < fieldName.length(); ) {
|
||||||
|
if(ic > 0 && (fieldName.charAt(ic) == '[' || fieldName.charAt(ic) == ']') &&
|
||||||
|
fieldName.charAt(ic - 1) == '\\') {
|
||||||
|
// replace '\[' with '['
|
||||||
|
fieldName = fieldName.substring(0, ic - 1) + fieldName.substring(ic);
|
||||||
|
} else if(fieldName.charAt(ic) == '[') {
|
||||||
|
indOpArray = ic;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
ic++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonListLocal = null; // reset
|
||||||
|
if(indOpArray == -1) {
|
||||||
|
if(!last) {
|
||||||
|
Map<String, Object> fieldAccess = (Map<String, Object>) jsonObjLocal.get(fieldName);
|
||||||
|
if(fieldAccess == null) {
|
||||||
|
if(op.operation == GET_OPERATION) {
|
||||||
|
// don't modify during get operation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<String, Object> newJsonMap = new TreeMap<>();
|
||||||
|
jsonObjLocal.put(fieldName, newJsonMap);
|
||||||
|
jsonObjLocal = newJsonMap;
|
||||||
|
} else {
|
||||||
|
jsonObjLocal = fieldAccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String arrayFieldName = fieldName.substring(0, indOpArray);
|
||||||
|
if(arrayFieldName.contains("]")) {
|
||||||
|
throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i]));
|
||||||
|
}
|
||||||
|
jsonListLocal = (List<Object>) jsonObjLocal.get(arrayFieldName);
|
||||||
|
if (jsonListLocal == null) {
|
||||||
|
if (op.operation == GET_OPERATION) {
|
||||||
|
// don't modify during get operation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
jsonListLocal = new ArrayList<Object>();
|
||||||
|
jsonObjLocal.put(arrayFieldName, jsonListLocal);
|
||||||
|
}
|
||||||
|
while (indOpArray != -1) {
|
||||||
|
fieldName = fieldName.substring(indOpArray + 1);
|
||||||
|
int indClArray = fieldName.indexOf("]");
|
||||||
|
if (indClArray == -1) {
|
||||||
|
throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i]));
|
||||||
|
}
|
||||||
|
if(indClArray == fieldName.length() - 1) {
|
||||||
|
indOpArray = -1;
|
||||||
|
} else if(fieldName.charAt(indClArray + 1) == '[') {
|
||||||
|
indOpArray = indClArray + 1;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i]));
|
||||||
|
}
|
||||||
|
int index = Integer.parseInt(fieldName.substring(0, indClArray));
|
||||||
|
if (last && indOpArray == -1) {
|
||||||
|
indexToAccess = index;
|
||||||
|
} else {
|
||||||
|
Object obj = null;
|
||||||
|
if (index < jsonListLocal.size() && index >= 0) {
|
||||||
|
obj = jsonListLocal.get(index);
|
||||||
|
} else if (op.operation == SET_OPERATION && (index == -1 || index == jsonListLocal.size())) {
|
||||||
|
index = jsonListLocal.size();
|
||||||
|
jsonListLocal.add(null);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Illegal access to array at position %d", index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == null) {
|
||||||
|
if (op.operation == GET_OPERATION) {
|
||||||
|
// don't modify during get operation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (indOpArray == -1) {
|
||||||
|
obj = new TreeMap<>();
|
||||||
|
} else {
|
||||||
|
obj = new ArrayList<Object>();
|
||||||
|
}
|
||||||
|
jsonListLocal.set(index, obj);
|
||||||
|
}
|
||||||
|
if(indOpArray != -1) {
|
||||||
|
jsonListLocal = (List<Object>) obj;
|
||||||
|
} else {
|
||||||
|
jsonObjLocal = (Map<String, Object>) obj;
|
||||||
|
jsonListLocal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(jsonListLocal != null) {
|
||||||
|
return accessListField(op, jsonListLocal, indexToAccess);
|
||||||
|
} else {
|
||||||
|
return accessObjField(op, jsonObjLocal, fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object accessObjField(OperationAccess op, Map<String, Object> jsonObjLocal, String fieldName) {
|
||||||
|
Object prevValue;
|
||||||
|
if (op.operation == DELETE_OPERATION) {
|
||||||
|
prevValue = jsonObjLocal.remove(fieldName);
|
||||||
|
} else if (op.operation == SET_OPERATION) {
|
||||||
|
prevValue = jsonObjLocal.put(fieldName, op.value);
|
||||||
|
} else {
|
||||||
|
prevValue = jsonObjLocal.get(fieldName);
|
||||||
|
}
|
||||||
|
return prevValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object accessListField(OperationAccess op, List<Object> jsonListLocal, int indexToAccess) {
|
||||||
|
Object prevValue;
|
||||||
|
int lastIndex = indexToAccess;
|
||||||
|
if (op.operation == DELETE_OPERATION) {
|
||||||
|
if (lastIndex >= jsonListLocal.size() || lastIndex < 0) {
|
||||||
|
prevValue = null;
|
||||||
|
} else {
|
||||||
|
prevValue = jsonListLocal.remove(lastIndex);
|
||||||
|
}
|
||||||
|
} else if (op.operation == SET_OPERATION) {
|
||||||
|
if (lastIndex == jsonListLocal.size() || lastIndex == -1) {
|
||||||
|
prevValue = null;
|
||||||
|
jsonListLocal.add(op.value);
|
||||||
|
} else if (lastIndex >= jsonListLocal.size() || lastIndex < 0) {
|
||||||
|
throw new IllegalArgumentException(String.format("Illegal access to %d position in array with size %d",
|
||||||
|
lastIndex, jsonListLocal.size()));
|
||||||
|
} else {
|
||||||
|
prevValue = jsonListLocal.set(lastIndex, op.value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (lastIndex >= jsonListLocal.size() || lastIndex < 0) {
|
||||||
|
prevValue = null;
|
||||||
|
} else {
|
||||||
|
prevValue = jsonListLocal.get(lastIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prevValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static List<Object> getIndexObjectByField(Object obj, List<String> field, List<Object> res) {
|
||||||
|
if(obj == null) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if(field.size() == 0) {
|
||||||
|
if(res == null) {
|
||||||
|
res = new ArrayList<Object>();
|
||||||
|
}
|
||||||
|
res.add(obj);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (obj instanceof Map) {
|
||||||
|
String fieldFirst = field.get(0);
|
||||||
|
Object value = ((Map<String, Object>) obj).get(fieldFirst);
|
||||||
|
return getIndexObjectByField(value, field.subList(1, field.size()), res);
|
||||||
|
} else if(obj instanceof Collection) {
|
||||||
|
for(Object o : ((Collection<Object>)obj)) {
|
||||||
|
res = getIndexObjectByField(o, field, res);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we need extract but there no field
|
||||||
|
LOGGER.warn(String.format("Can't access field %s for object %s", field, obj));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8
|
||||||
|
package org.openplacereviews.opendb.util.exception;
|
||||||
|
|
||||||
|
public class FailedVerificationException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -4936205097177668159L;
|
||||||
|
|
||||||
|
|
||||||
|
public FailedVerificationException(Exception e) {
|
||||||
|
super(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public FailedVerificationException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue