Add timeline tab and gpx statistic dialog and sync aidl

This commit is contained in:
Chumva 2019-01-18 14:51:39 +02:00
parent bd1709a306
commit 80eda68e01
51 changed files with 3031 additions and 204 deletions

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="@color/ctrl_active_light" />
<corners android:radius="@dimen/dialog_button_radius" />
</shape>
</item>
<item android:state_enabled="false">
<shape android:shape="rectangle">
<solid android:color="@color/ctrl_light" />
<corners android:radius="@dimen/dialog_button_radius" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@null" />
<corners android:radius="@dimen/dialog_button_radius" />
<stroke android:width="1dp" android:color="@color/app_bar_divider" />
</shape>
</item>
</selector>

View file

@ -0,0 +1,184 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/card_bg_color">
<LinearLayout
android:id="@+id/title_row"
android:layout_width="match_parent"
android:layout_height="@dimen/action_bar_height"
android:gravity="center_vertical">
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="0dp"
android:layout_height="@dimen/action_bar_height"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center_vertical"
android:letterSpacing="@dimen/title_letter_spacing"
android:maxLines="1"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard"
android:text="@string/timeline"
android:textColor="@color/app_bar_title_light"
android:textSize="@dimen/title_text_size"
app:typeface="@string/font_roboto_mono_bold" />
<ImageView
android:id="@+id/options"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/content_padding_standard"
android:layout_marginRight="@dimen/content_padding_standard"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingLeft="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding_half"
tools:src="@drawable/ic_action_other_menu"
tools:tint="@color/icon_light"
tools:visibility="visible" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/action_bar_descr_bottom_margin"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard"
android:text="@string/timeline_description"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/descr_text_size" />
<FrameLayout
android:id="@+id/monitoring_container"
android:layout_width="match_parent"
android:layout_height="@dimen/action_bar_height"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/monitoring_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:text="@string/monitoring_is_enabled"
android:textColor="@color/ctrl_active_light"
android:textSize="@dimen/descr_text_size"
app:typeface="@string/font_roboto_medium" />
<Switch
android:id="@+id/monitoring_switcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:background="@null"
android:clickable="false"
android:focusable="false" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/app_bar_divider" />
<LinearLayout
android:id="@+id/date_row"
android:layout_width="match_parent"
android:layout_height="@dimen/list_header_height"
android:background="@color/screen_bg_light"
android:orientation="horizontal"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_start_btn"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round_border"
android:drawablePadding="@dimen/content_padding_half"
android:ellipsize="end"
android:gravity="start|center_vertical"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:text="@string/start_date"
android:textColor="?attr/ctrl_active_color"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium" />
<View
android:layout_width="16dp"
android:layout_height="1dp"
android:layout_gravity="center_vertical"
android:background="@color/app_bar_divider" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_end_btn"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/content_padding_half"
android:layout_marginStart="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round_border"
android:drawablePadding="@dimen/content_padding_half"
android:ellipsize="end"
android:gravity="start|center_vertical"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:text="@string/end_date"
android:textColor="?attr/ctrl_active_color"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/last_telegram_update_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:gravity="center"
android:paddingTop="@dimen/content_padding_half"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/list_item_description_text_size"
android:visibility="gone"
app:typeface="@string/font_roboto_regular" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/list_item_content_margin"
android:scrollbars="vertical" />
</FrameLayout>
</LinearLayout>

View file

@ -0,0 +1,509 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/card_bg_color">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@+id/image_container"
android:layout_width="match_parent"
android:layout_height="120dp"
app:layout_scrollFlags="scroll">
<ImageView
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="@dimen/content_padding_standard"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/shared_string_back"
android:padding="@dimen/content_padding_standard"
tools:src="@drawable/ic_arrow_back"
tools:tint="@color/icon_light" />
<ImageView
android:id="@+id/user_icon"
android:layout_width="@dimen/my_location_user_icon_size"
android:layout_height="@dimen/my_location_user_icon_size"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/content_padding_standard"
tools:src="@drawable/img_user_picture" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="@string/start_location_sharing"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/title_text_size"
app:typeface="@string/font_roboto_mono_bold" />
</FrameLayout>
<LinearLayout
android:id="@+id/interval_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/start_descr"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/content_padding_standard"
android:layout_marginRight="@dimen/content_padding_standard"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/start_date"
android:textColor="?attr/android:textColorSecondary"
android:textSize="@dimen/list_item_description_text_size"
app:firstBaselineToTopHeight="@dimen/list_item_baseline_to_top_height"
app:typeface="@string/font_roboto_regular" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/end_descr"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/content_padding_standard"
android:layout_marginStart="@dimen/content_padding_standard"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/end_date"
android:textColor="?attr/android:textColorSecondary"
android:textSize="@dimen/list_item_description_text_size"
app:firstBaselineToTopHeight="@dimen/list_item_baseline_to_top_height"
app:typeface="@string/font_roboto_regular" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/list_header_height"
android:orientation="horizontal">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_start_btn"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium"
tools:textColor="?attr/ctrl_active_color"
tools:visibility="visible" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/time_start_btn"
android:layout_width="wrap_content"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:background="@drawable/btn_round"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium"
tools:textColor="?attr/ctrl_active_color"
tools:visibility="visible" />
<View
android:layout_width="16dp"
android:layout_height="1dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:background="@color/card_divider_light" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_end_btn"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium"
tools:textColor="?attr/ctrl_active_color"
tools:visibility="visible" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/time_end_btn"
android:layout_width="wrap_content"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:background="@drawable/btn_round"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium"
tools:textColor="?attr/ctrl_active_color"
tools:visibility="visible" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="152dp"
android:layout_marginLeft="@dimen/content_padding_standard"
android:layout_marginRight="@dimen/content_padding_standard">
<ImageView
android:id="@+id/gpx_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/shared_string_map" />
</FrameLayout>
<LinearLayout
android:id="@+id/gpx_statistic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/distance_time_span"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="11dp"
android:paddingTop="13dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingLeft="16dp">
<ImageView
android:id="@+id/distance_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@drawable/ic_action_polygom_dark" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:orientation="vertical">
<TextView
android:id="@+id/distance_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:background="@null"
android:text="@string/shared_string_distance"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/list_item_description_text_size" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/distance_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:text="40 km"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingLeft="16dp">
<ImageView
android:id="@+id/duration_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@drawable/ic_action_share_location" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:orientation="vertical">
<TextView
android:id="@+id/duration_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:background="@null"
android:text="@string/time_on_the_move"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/list_item_description_text_size" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/duration_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:text="3:32:44"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/list_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingRight="8dp">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/card_divider_light" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingLeft="8dp"
android:paddingRight="16dp">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/card_divider_light" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/start_end_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="11dp"
android:paddingTop="13dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingLeft="16dp">
<ImageView
android:id="@+id/average_speed_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@drawable/ic_action_speed_average" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:orientation="vertical">
<TextView
android:id="@+id/average_speed_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@null"
android:text="@string/average_speed"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/list_item_description_text_size" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/average_speed_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:text="15:04:58"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingLeft="16dp">
<ImageView
android:id="@+id/average_altitude_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@drawable/ic_action_share_location" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:orientation="vertical">
<TextView
android:id="@+id/average_altitude_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@null"
android:text="@string/average_altitude"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/list_item_description_text_size" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/average_altitude_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:text="20:58:00"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/open_in_osmand_btn"
android:layout_width="match_parent"
android:layout_height="@dimen/list_item_height_min"
android:layout_marginBottom="@dimen/content_padding_standard"
android:layout_marginLeft="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:background="@drawable/btn_round"
android:gravity="center_vertical"
android:paddingLeft="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding_half">
<ImageView
android:id="@+id/open_in_osmand_icon"
android:layout_width="@dimen/list_item_icon_size"
android:layout_height="@dimen/list_item_icon_size"
android:src="@drawable/ic_logo_osmand_free" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/open_in_osmand_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/content_padding_big"
android:layout_marginRight="@dimen/content_padding_big"
android:text="@string/open_in_osmand"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/list_item_title_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
<LinearLayout
android:id="@+id/share_gpx_btn"
android:layout_width="match_parent"
android:layout_height="@dimen/list_item_height_min"
android:layout_marginBottom="@dimen/content_padding_standard"
android:layout_marginLeft="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:background="@drawable/btn_round"
android:gravity="center_vertical"
android:paddingLeft="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding_half">
<ImageView
android:id="@+id/share_gpx_icon"
android:layout_width="@dimen/list_item_icon_size"
android:layout_height="@dimen/list_item_icon_size"
android:src="@drawable/ic_action_share"
android:tint="@color/ctrl_active_light" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/share_gpx_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/content_padding_big"
android:layout_marginRight="@dimen/content_padding_big"
android:text="@string/shared_string_share"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/list_item_title_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View file

@ -9,4 +9,9 @@
android:id="@+id/action_live_now" android:id="@+id/action_live_now"
android:icon="@drawable/ic_action_live_now" android:icon="@drawable/ic_action_live_now"
android:title="@string/live_now"/> android:title="@string/live_now"/>
<item
android:id="@+id/action_timeline"
android:icon="@drawable/ic_action_timeline"
android:title="@string/timeline"/>
</menu> </menu>

View file

@ -1,5 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources> <resources>
<string name="timeline_description">Включите мониторинг, для сбора данных о перемещении в фоновом режиме.</string>
<string name="last_update_from_telegram">Последнее обновление в Telegram</string> <string name="last_update_from_telegram">Последнее обновление в Telegram</string>
<string name="device_name">Имя устройства</string> <string name="device_name">Имя устройства</string>
<string name="shared_string_hide">Спрятать</string> <string name="shared_string_hide">Спрятать</string>

View file

@ -1,4 +1,13 @@
<resources> <resources>
<string name="monitoring_is_enabled">Monitoring is enabled</string>
<string name="monitoring_is_disabled">Monitoring is disabled</string>
<string name="time_on_the_move">time on the move</string>
<string name="average_altitude">Average altitude</string>
<string name="average_speed">Average speed</string>
<string name="open_in_osmand">Open in OsmAnd</string>
<string name="end_date">End date</string>
<string name="start_date">Start date</string>
<string name="timeline_description">Enable monitoring to collect movement data in the background.</string>
<string name="send_location_as">Send location as</string> <string name="send_location_as">Send location as</string>
<string name="send_location_as_descr">Choose how messages with your location will look like.</string> <string name="send_location_as_descr">Choose how messages with your location will look like.</string>
<string name="shared_string_map">Map</string> <string name="shared_string_map">Map</string>
@ -176,6 +185,7 @@
<string name="welcome_descr"><![CDATA[<b>OsmAnd Location Sharing</b> lets you share your location and see that of others in the OsmAnd.<br/><br/>The app uses Telegram API and you need a Telegram account.]]></string> <string name="welcome_descr"><![CDATA[<b>OsmAnd Location Sharing</b> lets you share your location and see that of others in the OsmAnd.<br/><br/>The app uses Telegram API and you need a Telegram account.]]></string>
<string name="my_location">My location</string> <string name="my_location">My location</string>
<string name="live_now">Live now</string> <string name="live_now">Live now</string>
<string name="timeline">Timeline</string>
</resources> </resources>

View file

@ -1,9 +1,14 @@
package net.osmand.aidl; package net.osmand.aidl;
import net.osmand.aidl.search.SearchResult; import net.osmand.aidl.search.SearchResult;
import net.osmand.aidl.gpx.AGpxBitmap;
interface IOsmAndAidlCallback { interface IOsmAndAidlCallback {
void onSearchComplete(in List<SearchResult> resultSet); void onSearchComplete(in List<SearchResult> resultSet);
void onUpdate(); void onUpdate();
void onAppInitialized();
void onGpxBitmapCreated(in AGpxBitmap bitmap);
} }

View file

@ -55,6 +55,9 @@ import net.osmand.aidl.maplayer.point.ShowMapPointParams;
import net.osmand.aidl.navdrawer.SetNavDrawerItemsParams; import net.osmand.aidl.navdrawer.SetNavDrawerItemsParams;
import net.osmand.aidl.navdrawer.NavDrawerFooterParams;
import net.osmand.aidl.navdrawer.NavDrawerHeaderParams;
import net.osmand.aidl.navigation.PauseNavigationParams; import net.osmand.aidl.navigation.PauseNavigationParams;
import net.osmand.aidl.navigation.ResumeNavigationParams; import net.osmand.aidl.navigation.ResumeNavigationParams;
import net.osmand.aidl.navigation.StopNavigationParams; import net.osmand.aidl.navigation.StopNavigationParams;
@ -67,6 +70,16 @@ import net.osmand.aidl.search.SearchResult;
import net.osmand.aidl.search.SearchParams; import net.osmand.aidl.search.SearchParams;
import net.osmand.aidl.navigation.NavigateSearchParams; import net.osmand.aidl.navigation.NavigateSearchParams;
import net.osmand.aidl.customization.SetWidgetsParams;
import net.osmand.aidl.customization.OsmandSettingsParams;
import net.osmand.aidl.gpx.AGpxFile;
import net.osmand.aidl.gpx.AGpxFileDetails;
import net.osmand.aidl.gpx.CreateGpxBitmapParams;
import net.osmand.aidl.tiles.ASqliteDbFile;
import net.osmand.aidl.plugins.PluginParams;
// NOTE: Add new methods at the end of file!!! // NOTE: Add new methods at the end of file!!!
interface IOsmAndAidlInterface { interface IOsmAndAidlInterface {
@ -133,4 +146,34 @@ interface IOsmAndAidlInterface {
long registerForUpdates(in long updateTimeMS, IOsmAndAidlCallback callback); long registerForUpdates(in long updateTimeMS, IOsmAndAidlCallback callback);
boolean unregisterFromUpdates(in long callbackId); boolean unregisterFromUpdates(in long callbackId);
boolean setNavDrawerLogo(in String imageUri);
boolean setEnabledIds(in List<String> ids);
boolean setDisabledIds(in List<String> ids);
boolean setEnabledPatterns(in List<String> patterns);
boolean setDisabledPatterns(in List<String> patterns);
boolean regWidgetVisibility(in SetWidgetsParams params);
boolean regWidgetAvailability(in SetWidgetsParams params);
boolean customizeOsmandSettings(in OsmandSettingsParams params);
boolean getImportedGpx(out List<AGpxFile> files);
boolean getSqliteDbFiles(out List<ASqliteDbFile> files);
boolean getActiveSqliteDbFiles(out List<ASqliteDbFile> files);
boolean showSqliteDbFile(String fileName);
boolean hideSqliteDbFile(String fileName);
boolean setNavDrawerLogoWithParams(in NavDrawerHeaderParams params);
boolean setNavDrawerFooterWithParams(in NavDrawerFooterParams params);
boolean restoreOsmand();
boolean changePluginState(in PluginParams params);
boolean registerForOsmandInitListener(in IOsmAndAidlCallback callback);
boolean getBitmapForGpx(in CreateGpxBitmapParams file, IOsmAndAidlCallback callback);
} }

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.customization;
parcelable OsmandSettingsParams;

View file

@ -0,0 +1,60 @@
package net.osmand.aidl.customization;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class OsmandSettingsParams implements Parcelable {
private String sharedPreferencesName;
private Bundle bundle;
public OsmandSettingsParams(@NonNull String sharedPreferencesName, @Nullable Bundle bundle) {
this.sharedPreferencesName = sharedPreferencesName;
this.bundle = bundle;
}
public OsmandSettingsParams(Parcel in) {
readFromParcel(in);
}
public static final Creator<OsmandSettingsParams> CREATOR = new Creator<OsmandSettingsParams>() {
@Override
public OsmandSettingsParams createFromParcel(Parcel in) {
return new OsmandSettingsParams(in);
}
@Override
public OsmandSettingsParams[] newArray(int size) {
return new OsmandSettingsParams[size];
}
};
public String getSharedPreferencesName() {
return sharedPreferencesName;
}
public Bundle getBundle() {
return bundle;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(sharedPreferencesName);
out.writeBundle(bundle);
}
@SuppressLint("ParcelClassLoader")
private void readFromParcel(Parcel in) {
sharedPreferencesName = in.readString();
bundle = in.readBundle();
}
@Override
public int describeContents() {
return 0;
}
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.customization;
parcelable SetWidgetsParams;

View file

@ -0,0 +1,93 @@
package net.osmand.aidl.customization;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class SetWidgetsParams implements Parcelable {
private String widgetKey;
private List<String> appModesKeys;
public SetWidgetsParams(String widgetKey, @Nullable List<String> appModesKeys) {
this.widgetKey = widgetKey;
this.appModesKeys = appModesKeys;
}
public SetWidgetsParams(Parcel in) {
readFromParcel(in);
}
public static final Creator<SetWidgetsParams> CREATOR = new Creator<SetWidgetsParams>() {
@Override
public SetWidgetsParams createFromParcel(Parcel in) {
return new SetWidgetsParams(in);
}
@Override
public SetWidgetsParams[] newArray(int size) {
return new SetWidgetsParams[size];
}
};
public String getWidgetKey() {
return widgetKey;
}
public List<String> getAppModesKeys() {
return appModesKeys;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(widgetKey);
writeStringList(out, appModesKeys);
}
private void readFromParcel(Parcel in) {
widgetKey = in.readString();
appModesKeys = readStringList(in);
}
@Override
public int describeContents() {
return 0;
}
private void writeStringList(Parcel out, List<String> val) {
if (val == null) {
out.writeInt(-1);
return;
}
int N = val.size();
int i = 0;
out.writeInt(N);
while (i < N) {
out.writeString(val.get(i));
i++;
}
}
private List<String> readStringList(Parcel in) {
List<String> list = new ArrayList<>();
int M = list.size();
int N = in.readInt();
if (N == -1) {
return null;
}
int i = 0;
for (; i < M && i < N; i++) {
list.set(i, in.readString());
}
for (; i < N; i++) {
list.add(in.readString());
}
for (; i < M; i++) {
list.remove(N);
}
return list;
}
}

View file

@ -0,0 +1,4 @@
package net.osmand.aidl.gpx;
parcelable AGpxBitmap;

View file

@ -0,0 +1,50 @@
package net.osmand.aidl.gpx;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
public class AGpxBitmap implements Parcelable {
private Bitmap bitmap;
public AGpxBitmap(@NonNull Bitmap bitmap) {
this.bitmap = bitmap;
}
public AGpxBitmap(Parcel in) {
readFromParcel(in);
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public static final Creator<AGpxBitmap> CREATOR = new
Creator<AGpxBitmap>() {
public AGpxBitmap createFromParcel(Parcel in) {
return new AGpxBitmap(in);
}
public AGpxBitmap[] newArray(int size) {
return new AGpxBitmap[size];
}
};
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(bitmap, flags);
}
private void readFromParcel(Parcel in) {
bitmap = in.readParcelable(Bitmap.class.getClassLoader());
}
public int describeContents() {
return 0;
}
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.gpx;
parcelable AGpxFile;

View file

@ -0,0 +1,89 @@
package net.osmand.aidl.gpx;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class AGpxFile implements Parcelable {
private String fileName;
private long modifiedTime;
private long fileSize;
private boolean active;
private AGpxFileDetails details;
public AGpxFile(@NonNull String fileName, long modifiedTime, long fileSize, boolean active, @Nullable AGpxFileDetails details) {
this.fileName = fileName;
this.modifiedTime = modifiedTime;
this.fileSize = fileSize;
this.active = active;
this.details = details;
}
public AGpxFile(Parcel in) {
readFromParcel(in);
}
public static final Creator<AGpxFile> CREATOR = new
Creator<AGpxFile>() {
public AGpxFile createFromParcel(Parcel in) {
return new AGpxFile(in);
}
public AGpxFile[] newArray(int size) {
return new AGpxFile[size];
}
};
public String getFileName() {
return fileName;
}
public long getModifiedTime() {
return modifiedTime;
}
public long getFileSize() {
return fileSize;
}
public boolean isActive() {
return active;
}
public AGpxFileDetails getDetails() {
return details;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(fileName);
out.writeLong(modifiedTime);
out.writeLong(fileSize);
out.writeByte((byte) (active ? 1 : 0));
out.writeByte((byte) (details != null ? 1 : 0));
if (details != null) {
out.writeParcelable(details, flags);
}
}
private void readFromParcel(Parcel in) {
fileName = in.readString();
modifiedTime = in.readLong();
fileSize = in.readLong();
active = in.readByte() != 0;
boolean hasDetails= in.readByte() != 0;
if (hasDetails) {
details = in.readParcelable(AGpxFileDetails.class.getClassLoader());
} else {
details = null;
}
}
public int describeContents() {
return 0;
}
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.gpx;
parcelable AGpxFileDetails;

View file

@ -0,0 +1,196 @@
package net.osmand.aidl.gpx;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class AGpxFileDetails implements Parcelable {
private float totalDistance = 0;
private int totalTracks = 0;
private long startTime = Long.MAX_VALUE;
private long endTime = Long.MIN_VALUE;
private long timeSpan = 0;
private long timeMoving = 0;
private float totalDistanceMoving = 0;
private double diffElevationUp = 0;
private double diffElevationDown = 0;
private double avgElevation = 0;
private double minElevation = 99999;
private double maxElevation = -100;
private float minSpeed = Float.MAX_VALUE;
private float maxSpeed = 0;
private float avgSpeed;
private int points;
private int wptPoints = 0;
private List<String> wptCategoryNames = new ArrayList<>();
public AGpxFileDetails(float totalDistance, int totalTracks,
long startTime, long endTime,
long timeSpan, long timeMoving, float totalDistanceMoving,
double diffElevationUp, double diffElevationDown,
double avgElevation, double minElevation, double maxElevation,
float minSpeed, float maxSpeed, float avgSpeed,
int points, int wptPoints, Set<String> wptCategoryNames) {
this.totalDistance = totalDistance;
this.totalTracks = totalTracks;
this.startTime = startTime;
this.endTime = endTime;
this.timeSpan = timeSpan;
this.timeMoving = timeMoving;
this.totalDistanceMoving = totalDistanceMoving;
this.diffElevationUp = diffElevationUp;
this.diffElevationDown = diffElevationDown;
this.avgElevation = avgElevation;
this.minElevation = minElevation;
this.maxElevation = maxElevation;
this.minSpeed = minSpeed;
this.maxSpeed = maxSpeed;
this.avgSpeed = avgSpeed;
this.points = points;
this.wptPoints = wptPoints;
if (wptCategoryNames != null) {
this.wptCategoryNames = new ArrayList<>(wptCategoryNames);
}
}
public AGpxFileDetails(Parcel in) {
readFromParcel(in);
}
public static final Creator<AGpxFileDetails> CREATOR = new
Creator<AGpxFileDetails>() {
public AGpxFileDetails createFromParcel(Parcel in) {
return new AGpxFileDetails(in);
}
public AGpxFileDetails[] newArray(int size) {
return new AGpxFileDetails[size];
}
};
public float getTotalDistance() {
return totalDistance;
}
public int getTotalTracks() {
return totalTracks;
}
public long getStartTime() {
return startTime;
}
public long getEndTime() {
return endTime;
}
public long getTimeSpan() {
return timeSpan;
}
public long getTimeMoving() {
return timeMoving;
}
public float getTotalDistanceMoving() {
return totalDistanceMoving;
}
public double getDiffElevationUp() {
return diffElevationUp;
}
public double getDiffElevationDown() {
return diffElevationDown;
}
public double getAvgElevation() {
return avgElevation;
}
public double getMinElevation() {
return minElevation;
}
public double getMaxElevation() {
return maxElevation;
}
public float getMinSpeed() {
return minSpeed;
}
public float getMaxSpeed() {
return maxSpeed;
}
public float getAvgSpeed() {
return avgSpeed;
}
public int getPoints() {
return points;
}
public int getWptPoints() {
return wptPoints;
}
public List<String> getWptCategoryNames() {
return wptCategoryNames;
}
public void writeToParcel(Parcel out, int flags) {
out.writeFloat(totalDistance);
out.writeInt(totalTracks);
out.writeLong(startTime);
out.writeLong(endTime);
out.writeLong(timeSpan);
out.writeLong(timeMoving);
out.writeFloat(totalDistanceMoving);
out.writeDouble(diffElevationUp);
out.writeDouble(diffElevationDown);
out.writeDouble(avgElevation);
out.writeDouble(minElevation);
out.writeDouble(maxElevation);
out.writeFloat(minSpeed);
out.writeFloat(maxSpeed);
out.writeFloat(avgSpeed);
out.writeInt(points);
out.writeInt(wptPoints);
out.writeStringList(wptCategoryNames);
}
private void readFromParcel(Parcel in) {
totalDistance = in.readFloat();
totalTracks = in.readInt();
startTime = in.readLong();
endTime = in.readLong();
timeSpan = in.readLong();
timeMoving = in.readLong();
totalDistanceMoving = in.readFloat();
diffElevationUp = in.readDouble();
diffElevationDown = in.readDouble();
avgElevation = in.readDouble();
minElevation = in.readDouble();
maxElevation = in.readDouble();
minSpeed = in.readFloat();
maxSpeed = in.readFloat();
avgSpeed = in.readFloat();
points = in.readInt();
wptPoints = in.readInt();
in.readStringList(wptCategoryNames);
}
public int describeContents() {
return 0;
}
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.gpx;
parcelable CreateGpxBitmapParams;

View file

@ -0,0 +1,101 @@
package net.osmand.aidl.gpx;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.File;
public class CreateGpxBitmapParams implements Parcelable {
private File gpxFile;
private Uri gpxUri;
private float density;
private int widthPixels;
private int heightPixels;
private int color; //ARGB color int
public CreateGpxBitmapParams(File gpxFile, float density, int widthPixels, int heightPixels, int color) {
this.gpxFile = gpxFile;
this.density = density;
this.widthPixels = widthPixels;
this.heightPixels = heightPixels;
this.color = color;
}
public CreateGpxBitmapParams(Uri gpxUri, float density, int widthPixels, int heightPixels, int color) {
this.gpxUri = gpxUri;
this.density = density;
this.widthPixels = widthPixels;
this.heightPixels = heightPixels;
this.color = color;
}
public CreateGpxBitmapParams(Parcel in) {
readFromParcel(in);
}
public static final Creator<CreateGpxBitmapParams> CREATOR = new
Creator<CreateGpxBitmapParams>() {
public CreateGpxBitmapParams createFromParcel(Parcel in) {
return new CreateGpxBitmapParams(in);
}
public CreateGpxBitmapParams[] newArray(int size) {
return new CreateGpxBitmapParams[size];
}
};
public File getGpxFile() {
return gpxFile;
}
public Uri getGpxUri() {
return gpxUri;
}
public int getWidthPixels() {
return widthPixels;
}
public int getHeightPixels() {
return heightPixels;
}
public float getDensity() {
return density;
}
public int getColor() {
return color;
}
public void writeToParcel(Parcel out, int flags) {
if (gpxFile != null) {
out.writeString(gpxFile.getAbsolutePath());
} else {
out.writeString(null);
}
out.writeParcelable(gpxUri, flags);
out.writeFloat(density);
out.writeInt(widthPixels);
out.writeInt(heightPixels);
out.writeInt(color);
}
private void readFromParcel(Parcel in) {
String gpxAbsolutePath = in.readString();
if (gpxAbsolutePath != null) {
gpxFile = new File(gpxAbsolutePath);
}
gpxUri = in.readParcelable(Uri.class.getClassLoader());
density = in.readFloat();
widthPixels = in.readInt();
heightPixels = in.readInt();
color = in.readInt();
}
public int describeContents() {
return 0;
}
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.navdrawer;
parcelable NavDrawerFooterParams;

View file

@ -0,0 +1,68 @@
package net.osmand.aidl.navdrawer;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class NavDrawerFooterParams implements Parcelable {
@NonNull
private String packageName;
@Nullable
private String intent;
@Nullable
private String appName;
@NonNull
public String getPackageName() {
return packageName;
}
@Nullable
public String getIntent() {
return intent;
}
@Nullable
public String getAppName() {
return appName;
}
public NavDrawerFooterParams(@NonNull String packageName, @Nullable String intent,
@Nullable String appName) {
this.packageName = packageName;
this.intent = intent;
this.appName = appName;
}
protected NavDrawerFooterParams(Parcel in) {
packageName = in.readString();
intent = in.readString();
appName = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
dest.writeString(intent);
dest.writeString(appName);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<NavDrawerFooterParams> CREATOR = new Creator<NavDrawerFooterParams>() {
@Override
public NavDrawerFooterParams createFromParcel(Parcel in) {
return new NavDrawerFooterParams(in);
}
@Override
public NavDrawerFooterParams[] newArray(int size) {
return new NavDrawerFooterParams[size];
}
};
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.navdrawer;
parcelable NavDrawerHeaderParams;

View file

@ -0,0 +1,68 @@
package net.osmand.aidl.navdrawer;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class NavDrawerHeaderParams implements Parcelable {
@NonNull
private String imageUri;
@NonNull
private String packageName;
@Nullable
private String intent;
@NonNull
public String getImageUri() {
return imageUri;
}
@NonNull
public String getPackageName() {
return packageName;
}
@Nullable
public String getIntent() {
return intent;
}
public NavDrawerHeaderParams(@NonNull String imageUri, @NonNull String packageName,
@Nullable String intent) {
this.imageUri = imageUri;
this.packageName = packageName;
this.intent = intent;
}
public NavDrawerHeaderParams(Parcel in) {
imageUri = in.readString();
packageName = in.readString();
intent = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(imageUri);
dest.writeString(packageName);
dest.writeString(intent);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<NavDrawerHeaderParams> CREATOR = new Creator<NavDrawerHeaderParams>() {
@Override
public NavDrawerHeaderParams createFromParcel(Parcel in) {
return new NavDrawerHeaderParams(in);
}
@Override
public NavDrawerHeaderParams[] newArray(int size) {
return new NavDrawerHeaderParams[size];
}
};
}

View file

@ -0,0 +1,5 @@
// PluginParams.aidl
package net.osmand.aidl.plugins;
parcelable PluginParams;

View file

@ -0,0 +1,51 @@
package net.osmand.aidl.plugins;
import android.os.Parcel;
import android.os.Parcelable;
public class PluginParams implements Parcelable {
private String pluginId;
private int newState; //0- off, 1 - on
public PluginParams(String pluginId, int newState) {
this.pluginId = pluginId;
this.newState = newState;
}
public String getPluginId() {
return pluginId;
}
public int getNewState() {
return newState;
}
protected PluginParams(Parcel in) {
pluginId = in.readString();
newState = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(pluginId);
dest.writeInt(newState);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<PluginParams> CREATOR = new Creator<PluginParams>() {
@Override
public PluginParams createFromParcel(Parcel in) {
return new PluginParams(in);
}
@Override
public PluginParams[] newArray(int size) {
return new PluginParams[size];
}
};
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.tiles;
parcelable ASqliteDbFile;

View file

@ -0,0 +1,69 @@
package net.osmand.aidl.tiles;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
public class ASqliteDbFile implements Parcelable {
private String fileName;
private long modifiedTime;
private long fileSize;
private boolean active;
public ASqliteDbFile(@NonNull String fileName, long modifiedTime, long fileSize, boolean active) {
this.fileName = fileName;
this.modifiedTime = modifiedTime;
this.fileSize = fileSize;
this.active = active;
}
public ASqliteDbFile(Parcel in) {
readFromParcel(in);
}
public static final Creator<ASqliteDbFile> CREATOR = new
Creator<ASqliteDbFile>() {
public ASqliteDbFile createFromParcel(Parcel in) {
return new ASqliteDbFile(in);
}
public ASqliteDbFile[] newArray(int size) {
return new ASqliteDbFile[size];
}
};
public String getFileName() {
return fileName;
}
public long getModifiedTime() {
return modifiedTime;
}
public long getFileSize() {
return fileSize;
}
public boolean isActive() {
return active;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(fileName);
out.writeLong(modifiedTime);
out.writeLong(fileSize);
out.writeByte((byte) (active ? 1 : 0));
}
private void readFromParcel(Parcel in) {
fileName = in.readString();
modifiedTime = in.readLong();
fileSize = in.readLong();
active = in.readByte() != 0;
}
public int describeContents() {
return 0;
}
}

View file

@ -79,6 +79,8 @@ private const val SHARE_CHATS_INFO_KEY = "share_chats_info"
private const val BATTERY_OPTIMISATION_ASKED = "battery_optimisation_asked" private const val BATTERY_OPTIMISATION_ASKED = "battery_optimisation_asked"
private const val MONITORING_ENABLED = "monitoring_enabled"
private const val SHARING_INITIALIZATION_TIME = 60 * 2L // 2 minutes private const val SHARING_INITIALIZATION_TIME = 60 * 2L // 2 minutes
private const val GPS_UPDATE_EXPIRED_TIME = 60 * 3L // 3 minutes private const val GPS_UPDATE_EXPIRED_TIME = 60 * 3L // 3 minutes
@ -111,6 +113,8 @@ class TelegramSettings(private val app: TelegramApplication) {
var batteryOptimisationAsked = false var batteryOptimisationAsked = false
var monitoringEnabled = false
init { init {
updatePrefs() updatePrefs()
read() read()
@ -435,6 +439,8 @@ class TelegramSettings(private val app: TelegramApplication) {
edit.putBoolean(BATTERY_OPTIMISATION_ASKED, batteryOptimisationAsked) edit.putBoolean(BATTERY_OPTIMISATION_ASKED, batteryOptimisationAsked)
edit.putBoolean(MONITORING_ENABLED, monitoringEnabled)
val jArray = convertShareChatsInfoToJson() val jArray = convertShareChatsInfoToJson()
if (jArray != null) { if (jArray != null) {
edit.putString(SHARE_CHATS_INFO_KEY, jArray.toString()) edit.putString(SHARE_CHATS_INFO_KEY, jArray.toString())
@ -491,6 +497,8 @@ class TelegramSettings(private val app: TelegramApplication) {
) )
batteryOptimisationAsked = prefs.getBoolean(BATTERY_OPTIMISATION_ASKED,false) batteryOptimisationAsked = prefs.getBoolean(BATTERY_OPTIMISATION_ASKED,false)
monitoringEnabled = prefs.getBoolean(MONITORING_ENABLED,false)
} }
private fun convertShareDevicesToJson():JSONObject?{ private fun convertShareDevicesToJson():JSONObject?{

View file

@ -75,6 +75,12 @@ class OsmandAidlHelper(private val app: TelegramApplication) {
fun onSearchComplete(resultSet: List<SearchResult>) fun onSearchComplete(resultSet: List<SearchResult>)
} }
private var gpxBitmapCreatedListener: GpxBitmapCreatedListener? = null
interface GpxBitmapCreatedListener {
fun onGpxBitmapCreated(bitmap: AGpxBitmap)
}
private val mIOsmAndAidlCallback = object : IOsmAndAidlCallback.Stub() { private val mIOsmAndAidlCallback = object : IOsmAndAidlCallback.Stub() {
@Throws(RemoteException::class) @Throws(RemoteException::class)
@ -90,12 +96,26 @@ class OsmandAidlHelper(private val app: TelegramApplication) {
mUpdatesListener!!.update() mUpdatesListener!!.update()
} }
} }
override fun onAppInitialized() {
}
override fun onGpxBitmapCreated(bitmap: AGpxBitmap) {
if (gpxBitmapCreatedListener != null) {
gpxBitmapCreatedListener!!.onGpxBitmapCreated(bitmap)
}
}
} }
fun setSearchCompleteListener(mSearchCompleteListener: SearchCompleteListener) { fun setSearchCompleteListener(mSearchCompleteListener: SearchCompleteListener) {
this.mSearchCompleteListener = mSearchCompleteListener this.mSearchCompleteListener = mSearchCompleteListener
} }
fun setGpxBitmapCreatedListener(gpxBitmapCreatedListener: GpxBitmapCreatedListener) {
this.gpxBitmapCreatedListener = gpxBitmapCreatedListener
}
private var mUpdatesListener: UpdatesListener? = null private var mUpdatesListener: UpdatesListener? = null
interface UpdatesListener { interface UpdatesListener {
@ -1059,4 +1079,16 @@ class OsmandAidlHelper(private val app: TelegramApplication) {
} }
return false return false
} }
fun getBitmapForGpx(gpxUri: Uri, density: Float, widthPixels: Int, heightPixels: Int, color: Int): Boolean {
if (mIOsmAndAidlInterface != null) {
try {
app.grantUriPermission(app.settings.appToConnectPackage, gpxUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
return mIOsmAndAidlInterface!!.getBitmapForGpx(CreateGpxBitmapParams(gpxUri, density, widthPixels, heightPixels, color), mIOsmAndAidlCallback)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
return false
}
} }

View file

@ -7,7 +7,6 @@ import android.os.AsyncTask;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.telegram.TelegramApplication; import net.osmand.telegram.TelegramApplication;
import net.osmand.telegram.ui.LiveNowTabFragment;
import net.osmand.telegram.utils.GPXUtilities; import net.osmand.telegram.utils.GPXUtilities;
import net.osmand.telegram.utils.GPXUtilities.GPXFile; import net.osmand.telegram.utils.GPXUtilities.GPXFile;
import net.osmand.telegram.utils.GPXUtilities.Track; import net.osmand.telegram.utils.GPXUtilities.Track;
@ -19,7 +18,6 @@ import org.drinkless.td.libcore.telegram.TdApi;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@ -50,7 +48,7 @@ public class SavingTracksDbHelper extends SQLiteOpenHelper {
+ TRACK_COL_ALTITUDE + " double, " + TRACK_COL_SPEED + " double, " //$NON-NLS-1$ //$NON-NLS-2$ + TRACK_COL_ALTITUDE + " double, " + TRACK_COL_SPEED + " double, " //$NON-NLS-1$ //$NON-NLS-2$
+ TRACK_COL_HDOP + " double, " + TRACK_COL_DATE + " long, " + TRACK_COL_TEXT_INFO + " int )"; + TRACK_COL_HDOP + " double, " + TRACK_COL_DATE + " long, " + TRACK_COL_TEXT_INFO + " int )";
public final static Log log = PlatformUtil.getLog(SavingTracksDbHelper.class); private final static Log log = PlatformUtil.getLog(SavingTracksDbHelper.class);
private final TelegramApplication app; private final TelegramApplication app;
@ -110,23 +108,32 @@ public class SavingTracksDbHelper extends SQLiteOpenHelper {
} }
} }
public void saveAsyncUserDataToGpx(LiveNowTabFragment fragment, File dir, int userId, long interval) { public void saveUserDataToGpx(SaveGpxListener listener, File dir, int userId, long chatId, long start, long end) {
GPXFile gpxFile = app.getSavingTracksDbHelper().collectRecordedDataForUser(userId, interval); GPXFile gpxFile = collectRecordedDataForUserAndChat(userId, chatId, start, end);
if (gpxFile != null && !gpxFile.isEmpty()) { if (gpxFile != null && !gpxFile.isEmpty()) {
SaveGPXTrackToFileTask task = new SaveGPXTrackToFileTask(fragment, gpxFile, dir, userId); SaveGPXTrackToFileTask task = new SaveGPXTrackToFileTask(app, listener, gpxFile, dir, userId);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
public void saveGpx(SaveGpxListener listener, File dir, GPXFile gpxFile) {
if (gpxFile != null && !gpxFile.isEmpty()) {
SaveGPXTrackToFileTask task = new SaveGPXTrackToFileTask(app, listener, gpxFile, dir, 0);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
} }
private void updateLocationMessage(TdApi.Message message) { private void updateLocationMessage(TdApi.Message message) {
log.debug(message);
TdApi.MessageContent content = message.content; TdApi.MessageContent content = message.content;
int senderId = app.getTelegramHelper().getSenderMessageId(message);
if (content instanceof TdApi.MessageLocation) { if (content instanceof TdApi.MessageLocation) {
long lastTextMessageUpdate = getLastTextTrackPointTimeForUser(message.senderUserId); long lastTextMessageUpdate = getLastTextTrackPointTimeForUser(message.senderUserId);
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
if (lastTextMessageUpdate == 0 || currentTime - lastTextMessageUpdate < 10 * 1000) { if (lastTextMessageUpdate == 0 || currentTime - lastTextMessageUpdate < 10 * 1000) {
log.debug("Add map message" + message.senderUserId); log.debug("Add map message " + message.senderUserId);
TdApi.MessageLocation messageLocation = (TdApi.MessageLocation) content; TdApi.MessageLocation messageLocation = (TdApi.MessageLocation) content;
insertData(message.senderUserId, message.chatId, messageLocation.location.latitude, insertData(senderId, message.chatId, messageLocation.location.latitude,
messageLocation.location.longitude, 0.0, 0.0, 0.0, messageLocation.location.longitude, 0.0, 0.0, 0.0,
Math.max(message.date, message.editDate), 0); Math.max(message.date, message.editDate), 0);
} else { } else {
@ -135,7 +142,7 @@ public class SavingTracksDbHelper extends SQLiteOpenHelper {
} else if (content instanceof TelegramHelper.MessageLocation) { } else if (content instanceof TelegramHelper.MessageLocation) {
log.debug("Add text message " + message.senderUserId); log.debug("Add text message " + message.senderUserId);
TelegramHelper.MessageLocation messageLocation = (TelegramHelper.MessageLocation) content; TelegramHelper.MessageLocation messageLocation = (TelegramHelper.MessageLocation) content;
insertData(message.senderUserId, message.chatId, messageLocation.getLat(), messageLocation.getLon(), insertData(senderId, message.chatId, messageLocation.getLat(), messageLocation.getLon(),
messageLocation.getAltitude(), messageLocation.getSpeed(), messageLocation.getHdop(), messageLocation.getAltitude(), messageLocation.getSpeed(), messageLocation.getHdop(),
messageLocation.getLastUpdated() * 1000L, 1); messageLocation.getLastUpdated() * 1000L, 1);
} }
@ -181,12 +188,12 @@ public class SavingTracksDbHelper extends SQLiteOpenHelper {
return res; return res;
} }
private GPXFile collectRecordedDataForUser(int userId, long interval) { public GPXFile collectRecordedDataForUserAndChat(int userId, long chatId, long start, long end) {
GPXFile gpxFile = null; GPXFile gpxFile = null;
SQLiteDatabase db = getReadableDatabase(); SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) { if (db != null && db.isOpen()) {
try { try {
gpxFile = collectDBTracksForUser(db, userId, interval); gpxFile = collectDBTracksForUser(db, userId, chatId, start, end);
} finally { } finally {
db.close(); db.close();
} }
@ -194,21 +201,52 @@ public class SavingTracksDbHelper extends SQLiteOpenHelper {
return gpxFile; return gpxFile;
} }
private GPXFile collectDBTracksForUser(SQLiteDatabase db, int userId, long interval) { public GPXFile collectRecordedDataForUser(int userId, long chatId, long start, long end) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + "," + TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ GPXFile gpxFile = null;
+ TRACK_COL_SPEED + "," + TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + SQLiteDatabase db = getReadableDatabase();
" WHERE " + TRACK_COL_USER_ID + " = ? ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId)}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ if (db != null && db.isOpen()) {
try {
if (chatId == 0) {
gpxFile = collectDBTracksForUser(db, userId, start, end);
} else {
gpxFile = collectDBTracksForUser(db, userId, chatId, start, end);
}
} finally {
db.close();
}
}
return gpxFile;
}
public ArrayList<GPXFile> collectRecordedDataForUsers(long start, long end, ArrayList<Integer> ignoredUsersIds) {
ArrayList<GPXFile> data = new ArrayList<>();
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
collectDBTracksForUsers(db, data, start, end, ignoredUsersIds);
} finally {
db.close();
}
}
return data;
}
private GPXFile collectDBTracksForUser(SQLiteDatabase db, int userId, long chatId, long start, long end) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ?"
+ " AND " + TRACK_COL_CHAT_ID + " = ?" + " AND " + TRACK_COL_DATE + " BETWEEN " + start + " AND " + end
+ " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId), String.valueOf(chatId)});
GPXFile gpxFile = new GPXFile(); GPXFile gpxFile = new GPXFile();
gpxFile.chatId = chatId;
gpxFile.userId = userId;
long previousTime = 0; long previousTime = 0;
TrkSegment segment = null; TrkSegment segment = null;
Track track = null; Track track = null;
if (query.moveToFirst()) { if (query.moveToFirst()) {
do { do {
long time = query.getLong(7); long time = query.getLong(7);
long curTime = System.currentTimeMillis();
if (curTime - time > interval) {
continue;
}
WptPt pt = new WptPt(); WptPt pt = new WptPt();
pt.userId = query.getInt(0); pt.userId = query.getInt(0);
pt.chatId = query.getLong(1); pt.chatId = query.getLong(1);
@ -244,19 +282,135 @@ public class SavingTracksDbHelper extends SQLiteOpenHelper {
return gpxFile; return gpxFile;
} }
private GPXFile collectDBTracksForUser(SQLiteDatabase db, int userId, long start, long end) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ?"
+ " AND " + TRACK_COL_DATE + " BETWEEN " + start + " AND " + end
+ " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId)});
GPXFile gpxFile = new GPXFile();
gpxFile.userId = userId;
long previousTime = 0;
TrkSegment segment = null;
Track track = null;
if (query.moveToFirst()) {
do {
long time = query.getLong(7);
WptPt pt = new WptPt();
pt.userId = query.getInt(0);
pt.chatId = query.getLong(1);
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpxFile.tracks.add(track);
}
previousTime = time;
} while (query.moveToNext());
}
query.close();
return gpxFile;
}
private void collectDBTracksForUsers(SQLiteDatabase db, ArrayList<GPXFile> dataTracks, long start, long end, ArrayList<Integer> ignoredUsersIds) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_DATE
+ " BETWEEN " + start + " AND " + end + " ORDER BY " + TRACK_COL_USER_ID + " ASC, "
+ TRACK_COL_CHAT_ID + " ASC, " + TRACK_COL_DATE + " ASC ", null);
long previousTime = 0;
long previousChatId = 0;
int previousUserId = 0;
TrkSegment segment = null;
Track track = null;
GPXFile gpx = new GPXFile();
if (query.moveToFirst()) {
do {
int userId = query.getInt(0);
if (ignoredUsersIds.contains(userId)) {
continue;
}
int chatId = query.getInt(1);
long time = query.getLong(7);
if (previousUserId != userId || previousChatId != chatId) {
gpx = new GPXFile();
gpx.chatId = chatId;
gpx.userId = userId;
previousTime = 0;
track = null;
segment = null;
dataTracks.add(gpx);
}
WptPt pt = new WptPt();
pt.userId = userId;
pt.chatId = chatId;
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpx.tracks.add(track);
}
previousTime = time;
previousUserId = userId;
previousChatId = chatId;
} while (query.moveToNext());
}
query.close();
}
private static class SaveGPXTrackToFileTask extends AsyncTask<Void, Void, List<String>> { private static class SaveGPXTrackToFileTask extends AsyncTask<Void, Void, List<String>> {
private TelegramApplication app; private TelegramApplication app;
private WeakReference<LiveNowTabFragment> fragmentRef; private SaveGpxListener listener;
private final GPXFile gpxFile; private final GPXFile gpxFile;
private File dir; private File dir;
private int userId; private int userId;
SaveGPXTrackToFileTask(LiveNowTabFragment fragment, GPXFile gpxFile, File dir, int userId) { SaveGPXTrackToFileTask(TelegramApplication app, SaveGpxListener listener, GPXFile gpxFile, File dir, int userId) {
this.gpxFile = gpxFile; this.gpxFile = gpxFile;
this.fragmentRef = new WeakReference<>(fragment); this.listener = listener;
this.app = (TelegramApplication) fragment.getActivity().getApplication(); this.app = app;
this.dir = dir; this.dir = dir;
this.userId = userId; this.userId = userId;
} }
@ -300,12 +454,21 @@ public class SavingTracksDbHelper extends SQLiteOpenHelper {
@Override @Override
protected void onPostExecute(List<String> warnings) { protected void onPostExecute(List<String> warnings) {
if (warnings != null && warnings.isEmpty()) { if (listener != null) {
LiveNowTabFragment fragment = fragmentRef.get(); if (warnings != null && warnings.isEmpty()) {
if (fragment != null && fragment.isResumed()) { listener.onSavingGpxFinish(gpxFile.path);
fragment.shareGpx(gpxFile.path); } else {
listener.onSavingGpxError(warnings);
} }
} }
} }
} }
public interface SaveGpxListener {
void onSavingGpxFinish(String path);
void onSavingGpxError(List<String> warnings);
}
} }

View file

@ -391,6 +391,15 @@ class TelegramHelper private constructor() {
fun isBot(userId: Int) = users[userId]?.type is TdApi.UserTypeBot fun isBot(userId: Int) = users[userId]?.type is TdApi.UserTypeBot
fun getSenderMessageId(message: TdApi.Message): Int {
val forwardInfo = message.forwardInfo
return if (forwardInfo != null && forwardInfo is TdApi.MessageForwardedFromUser) {
forwardInfo.senderUserId
} else {
message.senderUserId
}
}
fun startLiveMessagesUpdates(interval: Long) { fun startLiveMessagesUpdates(interval: Long) {
stopLiveMessagesUpdates() stopLiveMessagesUpdates()
@ -738,10 +747,13 @@ class TelegramHelper private constructor() {
} }
} else { } else {
removeOldMessages(message, fromBot, viaBot) removeOldMessages(message, fromBot, viaBot)
val oldMessage = usersLocationMessages.values.firstOrNull { it.senderUserId == message.senderUserId && !fromBot && !viaBot } val oldMessage = usersLocationMessages.values.firstOrNull { getSenderMessageId(it) == getSenderMessageId(message) && !fromBot && !viaBot }
if (oldMessage == null || (Math.max(message.editDate, message.date) > Math.max(oldMessage.editDate, oldMessage.date))) { val hasNewerMessage = oldMessage != null && (Math.max(message.editDate, message.date) < Math.max(oldMessage.editDate, oldMessage.date))
if (!hasNewerMessage) {
usersLocationMessages[message.id] = message usersLocationMessages[message.id] = message
incomingMessagesListeners.forEach { }
incomingMessagesListeners.forEach {
if (!hasNewerMessage || it is SavingTracksDbHelper) {
it.onReceiveChatLocationMessages(message.chatId, message) it.onReceiveChatLocationMessages(message.chatId, message)
} }
} }
@ -754,7 +766,7 @@ class TelegramHelper private constructor() {
while (iterator.hasNext()) { while (iterator.hasNext()) {
val message = iterator.next().value val message = iterator.next().value
if (newMessage.chatId == message.chatId) { if (newMessage.chatId == message.chatId) {
val sameSender = newMessage.senderUserId == message.senderUserId val sameSender = getSenderMessageId(newMessage) == getSenderMessageId(message)
val viaSameBot = newMessage.viaBotUserId == message.viaBotUserId val viaSameBot = newMessage.viaBotUserId == message.viaBotUserId
if (fromBot || viaBot) { if (fromBot || viaBot) {
if ((fromBot && sameSender) || (viaBot && viaSameBot)) { if ((fromBot && sameSender) || (viaBot && viaSameBot)) {
@ -1075,23 +1087,6 @@ class TelegramHelper private constructor() {
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true) return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
} }
/**
* @chatId Id of the chat
* @message Text of the message
*/
fun sendTextMessage(chatId: Long, message: String): Boolean {
// initialize reply markup just for testing
//val row = arrayOf(TdApi.InlineKeyboardButton("https://telegram.org?1", TdApi.InlineKeyboardButtonTypeUrl()), TdApi.InlineKeyboardButton("https://telegram.org?2", TdApi.InlineKeyboardButtonTypeUrl()), TdApi.InlineKeyboardButton("https://telegram.org?3", TdApi.InlineKeyboardButtonTypeUrl()))
//val replyMarkup = TdApi.ReplyMarkupInlineKeyboard(arrayOf(row, row, row))
if (haveAuthorization) {
val content = TdApi.InputMessageText(TdApi.FormattedText(message, null), false, true)
client?.send(TdApi.SendMessage(chatId, 0, false, true, null, content), defaultHandler)
return true
}
return false
}
fun logout(): Boolean { fun logout(): Boolean {
return if (libraryLoaded) { return if (libraryLoaded) {
haveAuthorization = false haveAuthorization = false

View file

@ -8,6 +8,7 @@ import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation
import net.osmand.telegram.utils.GPXUtilities
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
object TelegramUiHelper { object TelegramUiHelper {
@ -129,6 +130,10 @@ object TelegramUiHelper {
} }
} }
fun gpxToChatItem(helper: TelegramHelper, gpx: GPXUtilities.GPXFile, simpleUserItem: Boolean): GpxChatItem? {
return if (simpleUserItem) gpxToUserGpxChatItem(helper, gpx) else gpxToGpxChatItem(helper, gpx)
}
private fun botMessageToLocationItem( private fun botMessageToLocationItem(
chat: TdApi.Chat, chat: TdApi.Chat,
content: MessageOsmAndBotLocation content: MessageOsmAndBotLocation
@ -224,6 +229,49 @@ object TelegramUiHelper {
} }
} }
private fun gpxToGpxChatItem(
helper: TelegramHelper,
gpx: GPXUtilities.GPXFile
): GpxChatItem? {
val user = helper.getUser(gpx.userId) ?: return null
val chat = helper.getChat(gpx.chatId) ?: return null
return GpxChatItem().apply {
chatId = chat.id
chatTitle = chat.title
gpxFile = gpx
name = TelegramUiHelper.getUserName(user)
if (helper.isGroup(chat)) {
photoPath = helper.getUserPhotoPath(user)
groupPhotoPath = chat.photo?.small?.local?.path
} else {
photoPath = user.profilePhoto?.small?.local?.path
}
grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture
userId = user.id
privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat)
chatWithBot = helper.isBot(userId)
lastUpdated = (gpx.modifiedTime / 1000).toInt()
}
}
private fun gpxToUserGpxChatItem(
helper: TelegramHelper,
gpx: GPXUtilities.GPXFile
): GpxChatItem? {
val user = helper.getUser(gpx.userId) ?: return null
return GpxChatItem().apply {
gpxFile = gpx
name = TelegramUiHelper.getUserName(user)
photoPath = user.profilePhoto?.small?.local?.path
grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture
userId = user.id
chatWithBot = helper.isBot(userId)
lastUpdated = (gpx.modifiedTime / 1000).toInt()
}
}
abstract class ListItem { abstract class ListItem {
var chatId: Long = 0 var chatId: Long = 0
@ -272,6 +320,24 @@ object TelegramUiHelper {
override fun getVisibleName() = chatTitle override fun getVisibleName() = chatTitle
} }
class GpxChatItem : ListItem() {
var gpxFile: GPXUtilities.GPXFile? = null
internal set
var groupPhotoPath: String? = null
internal set
var privateChat: Boolean = false
internal set
var chatWithBot: Boolean = false
internal set
override fun canBeOpenedOnMap() = latLon != null
override fun getMapPointId() = "${chatId}_$userId"
override fun getVisibleName() = chatTitle
}
class LocationItem : ListItem() { class LocationItem : ListItem() {
override fun canBeOpenedOnMap() = latLon != null override fun canBeOpenedOnMap() = latLon != null

View file

@ -11,10 +11,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.ArrayAdapter import android.widget.*
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import net.osmand.Location import net.osmand.Location
import net.osmand.data.LatLon import net.osmand.data.LatLon
import net.osmand.telegram.R import net.osmand.telegram.R
@ -22,6 +19,7 @@ import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener
import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener
import net.osmand.telegram.TelegramSettings import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.helpers.SavingTracksDbHelper
import net.osmand.telegram.helpers.TelegramHelper.* import net.osmand.telegram.helpers.TelegramHelper.*
import net.osmand.telegram.helpers.TelegramUiHelper import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.helpers.TelegramUiHelper.ChatItem import net.osmand.telegram.helpers.TelegramUiHelper.ChatItem
@ -33,7 +31,7 @@ import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.UiUtils.UpdateLocationViewCache import net.osmand.telegram.utils.UiUtils.UpdateLocationViewCache
import net.osmand.util.MapUtils import net.osmand.util.MapUtils
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
import java.io.File import java.util.*
private const val CHAT_VIEW_TYPE = 0 private const val CHAT_VIEW_TYPE = 0
private const val LOCATION_ITEM_VIEW_TYPE = 1 private const val LOCATION_ITEM_VIEW_TYPE = 1
@ -233,15 +231,6 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
stopLocationUpdate() stopLocationUpdate()
} }
fun shareGpx(path: String) {
val fileUri = AndroidUtils.getUriForFile(app, File(path))
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
sendIntent.type = "application/gpx+xml"
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(sendIntent)
}
private fun chooseOsmAnd() { private fun chooseOsmAnd() {
val ctx = context ?: return val ctx = context ?: return
val installedApps = TelegramSettings.AppConnect.getInstalledApps(ctx) val installedApps = TelegramSettings.AppConnect.getInstalledApps(ctx)
@ -456,15 +445,6 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
app.showLocationHelper.showLocationOnMap(item, staleLocation) app.showLocationHelper.showLocationOnMap(item, staleLocation)
} }
} }
openOnMapView?.setOnLongClickListener {
app.savingTracksDbHelper.saveAsyncUserDataToGpx(
this@LiveNowTabFragment,
app.getExternalFilesDir(null),
item.userId,
60 * 60 * 24 * 1000
)
true
}
} else { } else {
openOnMapView?.setOnClickListener(null) openOnMapView?.setOnClickListener(null)
} }
@ -485,17 +465,6 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
if (lastItem) { if (lastItem) {
holder.lastTelegramUpdateTime?.visibility = View.VISIBLE holder.lastTelegramUpdateTime?.visibility = View.VISIBLE
holder.lastTelegramUpdateTime?.text = OsmandFormatter.getListItemLiveTimeDescr(app, telegramHelper.lastTelegramUpdateTime, lastTelegramUpdateStr) holder.lastTelegramUpdateTime?.text = OsmandFormatter.getListItemLiveTimeDescr(app, telegramHelper.lastTelegramUpdateTime, lastTelegramUpdateStr)
holder.lastTelegramUpdateTime?.setOnClickListener {
val currentUserId = telegramHelper.getCurrentUser()?.id
if (currentUserId != null) {
app.savingTracksDbHelper.saveAsyncUserDataToGpx(
this@LiveNowTabFragment,
app.getExternalFilesDir(null),
currentUserId,
60 * 60 * 24 * 1000
)
}
}
} else { } else {
holder.lastTelegramUpdateTime?.visibility = View.GONE holder.lastTelegramUpdateTime?.visibility = View.GONE
} }

View file

@ -27,6 +27,7 @@ import net.osmand.telegram.ui.MyLocationTabFragment.ActionButtonsListener
import net.osmand.telegram.ui.views.LockableViewPager import net.osmand.telegram.ui.views.LockableViewPager
import net.osmand.telegram.utils.* import net.osmand.telegram.utils.*
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
import java.io.File
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
const val OPEN_MY_LOCATION_TAB_KEY = "open_my_location_tab" const val OPEN_MY_LOCATION_TAB_KEY = "open_my_location_tab"
@ -35,6 +36,7 @@ private const val PERMISSION_REQUEST_LOCATION = 1
private const val MY_LOCATION_TAB_POS = 0 private const val MY_LOCATION_TAB_POS = 0
private const val LIVE_NOW_TAB_POS = 1 private const val LIVE_NOW_TAB_POS = 1
private const val TIMELINE_TAB_POS = 2
class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListener, TelegramIncomingMessagesListener { class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListener, TelegramIncomingMessagesListener {
@ -54,6 +56,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
private var myLocationTabFragment: MyLocationTabFragment? = null private var myLocationTabFragment: MyLocationTabFragment? = null
private var liveNowTabFragment: LiveNowTabFragment? = null private var liveNowTabFragment: LiveNowTabFragment? = null
private var timelineTabFragment: TimelineTabFragment? = null
private lateinit var buttonsBar: LinearLayout private lateinit var buttonsBar: LinearLayout
private lateinit var bottomNav: BottomNavigationView private lateinit var bottomNav: BottomNavigationView
@ -72,7 +75,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
val viewPager = findViewById<LockableViewPager>(R.id.view_pager).apply { val viewPager = findViewById<LockableViewPager>(R.id.view_pager).apply {
swipeLocked = true swipeLocked = true
offscreenPageLimit = 2 offscreenPageLimit = 3
adapter = ViewPagerAdapter(supportFragmentManager) adapter = ViewPagerAdapter(supportFragmentManager)
} }
@ -82,11 +85,13 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
when (it.itemId) { when (it.itemId) {
R.id.action_my_location -> pos = MY_LOCATION_TAB_POS R.id.action_my_location -> pos = MY_LOCATION_TAB_POS
R.id.action_live_now -> pos = LIVE_NOW_TAB_POS R.id.action_live_now -> pos = LIVE_NOW_TAB_POS
R.id.action_timeline -> pos = TIMELINE_TAB_POS
} }
if (pos != -1 && pos != viewPager.currentItem) { if (pos != -1 && pos != viewPager.currentItem) {
when (pos) { when (pos) {
MY_LOCATION_TAB_POS -> liveNowTabFragment?.tabClosed() MY_LOCATION_TAB_POS -> liveNowTabFragment?.tabClosed()
LIVE_NOW_TAB_POS -> liveNowTabFragment?.tabOpened() LIVE_NOW_TAB_POS -> liveNowTabFragment?.tabOpened()
TIMELINE_TAB_POS -> liveNowTabFragment?.tabClosed()
} }
viewPager.currentItem = pos viewPager.currentItem = pos
return@setOnNavigationItemSelectedListener true return@setOnNavigationItemSelectedListener true
@ -139,10 +144,10 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
if (fragment is TelegramListener) { if (fragment is TelegramListener) {
listeners.add(WeakReference(fragment)) listeners.add(WeakReference(fragment))
} }
if (fragment is MyLocationTabFragment) { when (fragment) {
myLocationTabFragment = fragment is MyLocationTabFragment -> myLocationTabFragment = fragment
} else if (fragment is LiveNowTabFragment) { is LiveNowTabFragment -> liveNowTabFragment = fragment
liveNowTabFragment = fragment is TimelineTabFragment -> timelineTabFragment = fragment
} }
} }
@ -317,6 +322,15 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
android.os.Process.killProcess(android.os.Process.myPid()) android.os.Process.killProcess(android.os.Process.myPid())
} }
fun shareGpx(path: String) {
val fileUri = AndroidUtils.getUriForFile(app, File(path))
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
sendIntent.type = "application/gpx+xml"
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(sendIntent)
}
fun loginTelegram() { fun loginTelegram() {
if (telegramHelper.getTelegramAuthorizationState() != TelegramAuthorizationState.CLOSED) { if (telegramHelper.getTelegramAuthorizationState() != TelegramAuthorizationState.CLOSED) {
telegramHelper.logout() telegramHelper.logout()
@ -463,7 +477,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
class ViewPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { class ViewPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
private val fragments = listOf<Fragment>(MyLocationTabFragment(), LiveNowTabFragment()) private val fragments = listOf<Fragment>(MyLocationTabFragment(), LiveNowTabFragment(), TimelineTabFragment())
override fun getItem(position: Int) = fragments[position] override fun getItem(position: Int) = fragments[position]

View file

@ -599,12 +599,6 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
} }
holder.title?.text = title holder.title?.text = title
holder.icon?.setOnClickListener {
app.forceUpdateMyLocation()
val curUser = telegramHelper.getCurrentUser()
val text = "${curUser?.id} ${curUser?.firstName} ${curUser?.lastName}"
Toast.makeText(app, text, Toast.LENGTH_LONG).show()
}
if (holder is ChatViewHolder) { if (holder is ChatViewHolder) {
holder.description?.visibility = View.GONE holder.description?.visibility = View.GONE
if (live) { if (live) {

View file

@ -0,0 +1,261 @@
package net.osmand.telegram.ui
import android.app.DatePickerDialog
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.support.annotation.DrawableRes
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Switch
import android.widget.TextView
import net.osmand.PlatformUtil
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandFormatter
import java.util.*
class TimelineTabFragment : Fragment() {
private val log = PlatformUtil.getLog(TimelineTabFragment::class.java)
private val app: TelegramApplication
get() = activity?.application as TelegramApplication
private val telegramHelper get() = app.telegramHelper
private val settings get() = app.settings
private lateinit var adapter: LiveNowListAdapter
private lateinit var dateStartBtn: TextView
private lateinit var dateEndBtn: TextView
private lateinit var mainView: View
private var start = 0L
private var end = 0L
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mainView = inflater.inflate(R.layout.fragment_timeline_tab, container, false)
val appBarLayout = mainView.findViewById<View>(R.id.app_bar_layout)
start = System.currentTimeMillis()
end = System.currentTimeMillis()
AndroidUtils.addStatusBarPadding19v(context!!, appBarLayout)
adapter = LiveNowListAdapter()
mainView.findViewById<RecyclerView>(R.id.recycler_view).apply {
layoutManager = LinearLayoutManager(context)
adapter = this@TimelineTabFragment.adapter
}
val switcher = mainView.findViewById<Switch>(R.id.monitoring_switcher)
val monitoringTv = mainView.findViewById<TextView>(R.id.monitoring_title)
monitoringTv.setText(if (settings.monitoringEnabled) R.string.monitoring_is_enabled else R.string.monitoring_is_disabled)
mainView.findViewById<View>(R.id.monitoring_container).setOnClickListener {
val monitoringEnabled = !settings.monitoringEnabled
settings.monitoringEnabled = monitoringEnabled
switcher.isChecked = monitoringEnabled
monitoringTv.setText(if (monitoringEnabled) R.string.monitoring_is_enabled else R.string.monitoring_is_disabled)
}
dateStartBtn = mainView.findViewById<TextView>(R.id.date_start_btn).apply {
setOnClickListener {
selectStartDate()
}
setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_start), null, null, null)
}
dateEndBtn = mainView.findViewById<TextView>(R.id.date_end_btn).apply {
setOnClickListener {
selectEndDate()
}
setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_add), null, null, null)
}
setupBtnTextColor(dateStartBtn)
setupBtnTextColor(dateEndBtn)
return mainView
}
private fun setupBtnTextColor(textView: TextView) {
textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light))
}
private fun selectStartDate() {
val dateFromDialog =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val from = Calendar.getInstance()
from.set(Calendar.YEAR, year)
from.set(Calendar.MONTH, monthOfYear)
from.set(Calendar.DAY_OF_MONTH, dayOfMonth)
start = from.timeInMillis
updateList()
updateDateButtons()
}
val startCalendar = Calendar.getInstance()
startCalendar.timeInMillis = start
DatePickerDialog(context, dateFromDialog,
startCalendar.get(Calendar.YEAR),
startCalendar.get(Calendar.MONTH),
startCalendar.get(Calendar.DAY_OF_MONTH)
).show()
}
private fun selectEndDate() {
val dateFromDialog =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val from = Calendar.getInstance()
from.set(Calendar.YEAR, year)
from.set(Calendar.MONTH, monthOfYear)
from.set(Calendar.DAY_OF_MONTH, dayOfMonth)
end = from.timeInMillis
updateList()
updateDateButtons()
}
val endCalendar = Calendar.getInstance()
endCalendar.timeInMillis = end
DatePickerDialog(context, dateFromDialog,
endCalendar.get(Calendar.YEAR),
endCalendar.get(Calendar.MONTH),
endCalendar.get(Calendar.DAY_OF_MONTH)
).show()
}
private fun updateDateButtons() {
dateStartBtn.text = OsmandFormatter.getFormattedDate(start / 1000)
dateEndBtn.text = OsmandFormatter.getFormattedDate(end / 1000)
dateEndBtn.setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_end), null, null, null)
}
private fun getPressedStateIcon(@DrawableRes iconId: Int): Drawable? {
val normal = app.uiUtils.getActiveIcon(iconId)
if (Build.VERSION.SDK_INT >= 21) {
val active = app.uiUtils.getIcon(iconId, R.color.ctrl_light)
if (normal != null && active != null) {
return AndroidUtils.createPressedStateListDrawable(normal, active)
}
}
return normal
}
private fun updateList() {
val res = mutableListOf<ListItem>()
val s = System.currentTimeMillis()
log.debug("updateList $start")
val ignoredUsersIds = ArrayList<Int>()
val currentUserId = telegramHelper.getCurrentUser()?.id
if (currentUserId != null) {
val currentUserGpx = app.savingTracksDbHelper.collectRecordedDataForUser(currentUserId, 0, start, end)
TelegramUiHelper.gpxToChatItem(telegramHelper, currentUserGpx, true)?.also {
res.add(it)
}
ignoredUsersIds.add(currentUserId)
}
val gpxFiles = app.savingTracksDbHelper.collectRecordedDataForUsers(start, end, ignoredUsersIds)
val e = System.currentTimeMillis()
gpxFiles.forEach {
TelegramUiHelper.gpxToChatItem(telegramHelper, it,false)?.also { chatItem ->
res.add(chatItem)
}
}
adapter.items = sortAdapterItems(res)
log.debug("updateList $s dif: ${e - s}")
}
private fun sortAdapterItems(list: MutableList<ListItem>): MutableList<ListItem> {
val currentUserId = telegramHelper.getCurrentUser()?.id ?: 0
list.sortWith(java.util.Comparator { lhs, rhs ->
when (currentUserId) {
lhs.userId -> return@Comparator 1
rhs.userId -> return@Comparator 1
else -> return@Comparator lhs.name.compareTo(rhs.name)
}
})
return list
}
inner class LiveNowListAdapter : RecyclerView.Adapter<BaseViewHolder>() {
var items: List<ListItem> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val inflater = LayoutInflater.from(parent.context)
return BaseViewHolder(inflater.inflate(R.layout.live_now_chat_card, parent, false))
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val lastItem = position == itemCount - 1
val item = items[position]
val currentUserId = telegramHelper.getCurrentUser()?.id ?: 0
TelegramUiHelper.setupPhoto(app, holder.icon, item.photoPath, R.drawable.img_user_picture_active, false)
holder.title?.text = item.name
holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE
holder.lastTelegramUpdateTime?.visibility = View.GONE
if (item is TelegramUiHelper.GpxChatItem) {
val gpx = item.gpxFile
val groupDescrRowVisible = (!item.privateChat || item.chatWithBot) && item.userId != currentUserId
if (groupDescrRowVisible) {
holder.groupDescrContainer?.visibility = View.VISIBLE
holder.groupTitle?.text = item.getVisibleName()
TelegramUiHelper.setupPhoto(app, holder.groupImage, item.groupPhotoPath, item.placeholderId, false)
} else {
holder.groupDescrContainer?.visibility = View.GONE
}
holder.userRow?.setOnClickListener {
if (gpx != null) {
childFragmentManager.also {
UserGpxInfoFragment.showInstance(it, gpx, start, end)
}
}
}
holder.imageButton?.visibility = View.GONE
holder.showOnMapRow?.visibility = View.GONE
holder.bottomDivider?.visibility = if (lastItem) View.GONE else View.VISIBLE
holder.topDivider?.visibility = if (position != 0) View.GONE else View.VISIBLE
}
}
override fun getItemCount() = items.size
inner class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val icon: ImageView? = view.findViewById(R.id.icon)
val title: TextView? = view.findViewById(R.id.title)
val description: TextView? = view.findViewById(R.id.description)
val bottomShadow: View? = view.findViewById(R.id.bottom_shadow)
val lastTelegramUpdateTime: TextView? = view.findViewById(R.id.last_telegram_update_time)
val userRow: View? = view.findViewById(R.id.user_row)
val groupDescrContainer: View? = view.findViewById(R.id.group_container)
val groupImage: ImageView? = view.findViewById(R.id.group_icon)
val groupTitle: TextView? = view.findViewById(R.id.group_title)
val imageButton: ImageView? = view.findViewById(R.id.image_button)
val showOnMapRow: View? = view.findViewById(R.id.show_on_map_row)
val topDivider: View? = view.findViewById(R.id.top_divider)
val bottomDivider: View? = view.findViewById(R.id.bottom_divider)
}
}
}

View file

@ -0,0 +1,322 @@
package net.osmand.telegram.ui
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.support.v4.app.FragmentManager
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import net.osmand.PlatformUtil
import net.osmand.aidl.gpx.AGpxBitmap
import net.osmand.telegram.R
import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.SavingTracksDbHelper
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.util.Algorithms
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
class UserGpxInfoFragment : BaseDialogFragment() {
private val log = PlatformUtil.getLog(UserGpxInfoFragment::class.java)
private val uiUtils get() = app.uiUtils
private lateinit var gpxFile: GPXUtilities.GPXFile
private lateinit var dateStartBtn: TextView
private lateinit var timeStartBtn: TextView
private lateinit var dateEndBtn: TextView
private lateinit var timeEndBtn: TextView
private lateinit var avgElevationTv: TextView
private lateinit var avgSpeedTv: TextView
private lateinit var totalDistanceTv: TextView
private lateinit var timeSpanTv: TextView
private var startCalendar = Calendar.getInstance()
private var endCalendar = Calendar.getInstance()
override fun onCreateView(
inflater: LayoutInflater,
parent: ViewGroup?,
savedInstanceState: Bundle?
): View {
val mainView = inflater.inflate(R.layout.fragment_user_gpx_info, parent)
AndroidUtils.addStatusBarPadding19v(context!!, mainView)
readFromBundle(savedInstanceState ?: arguments)
val userId = gpxFile.userId
val chatId = gpxFile.chatId
val user = app.telegramHelper.getUser(userId)
if (user != null) {
mainView.findViewById<TextView>(R.id.title).text = TelegramUiHelper.getUserName(user)
TelegramUiHelper.setupPhoto(app, mainView.findViewById<ImageView>(R.id.user_icon),
telegramHelper.getUserPhotoPath(user), R.drawable.img_user_placeholder, false)
}
app.osmandAidlHelper.setGpxBitmapCreatedListener(
object : OsmandAidlHelper.GpxBitmapCreatedListener {
override fun onGpxBitmapCreated(bitmap: AGpxBitmap) {
activity?.runOnUiThread {
mainView.findViewById<ImageView>(R.id.gpx_map).setImageDrawable(BitmapDrawable(app.resources, bitmap.bitmap))
}
}
})
updateGPXMap()
val backBtn = mainView.findViewById<ImageView>(R.id.back_button)
backBtn.setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_arrow_back))
backBtn.setOnClickListener {
dismiss()
}
dateStartBtn = mainView.findViewById<TextView>(R.id.date_start_btn)
timeStartBtn = mainView.findViewById<TextView>(R.id.time_start_btn)
dateEndBtn = mainView.findViewById<TextView>(R.id.date_end_btn)
timeEndBtn = mainView.findViewById<TextView>(R.id.time_end_btn)
dateStartBtn.setOnClickListener { selectStartDate() }
timeStartBtn.setOnClickListener { selectStartTime() }
dateEndBtn.setOnClickListener { selectEndDate() }
timeEndBtn.setOnClickListener { selectEndTime() }
setupBtnTextColor(dateStartBtn)
setupBtnTextColor(timeStartBtn)
setupBtnTextColor(dateEndBtn)
setupBtnTextColor(timeEndBtn)
updateDateAndTimeButtons()
avgElevationTv = mainView.findViewById<TextView>(R.id.average_altitude_text)
avgSpeedTv = mainView.findViewById<TextView>(R.id.average_speed_text)
totalDistanceTv = mainView.findViewById<TextView>(R.id.distance_text)
timeSpanTv = mainView.findViewById<TextView>(R.id.duration_text)
mainView.findViewById<ImageView>(R.id.average_altitude_icon).apply {
setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_altitude_range))
}
mainView.findViewById<ImageView>(R.id.average_speed_icon).apply {
setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_speed_average))
}
mainView.findViewById<ImageView>(R.id.distance_icon).apply {
setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_altitude_range))
}
mainView.findViewById<ImageView>(R.id.duration_icon).apply {
setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_altitude_range))
}
updateGPXStatisticRow()
mainView.findViewById<ImageView>(R.id.open_in_osmand_icon).setImageResource(R.drawable.ic_logo_osmand_free)
mainView.findViewById<LinearLayout>(R.id.open_in_osmand_btn).apply {
setOnClickListener {
val gpx = gpxFile
if (gpx.path.isNotEmpty()) {
openGpx(gpx.path)
} else {
saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener {
override fun onSavingGpxFinish(path: String) {
openGpx(path)
}
override fun onSavingGpxError(warnings: MutableList<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
}
})
}
}
}
mainView.findViewById<ImageView>(R.id.share_gpx_icon).setImageDrawable(uiUtils.getActiveIcon(R.drawable.ic_action_share))
mainView.findViewById<LinearLayout>(R.id.share_gpx_btn).apply {
setOnClickListener {
val gpx = gpxFile
if (gpx.path.isNotEmpty()) {
(activity as MainActivity).shareGpx(gpx.path)
} else {
saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener {
override fun onSavingGpxFinish(path: String) {
(activity as MainActivity).shareGpx(path)
}
override fun onSavingGpxError(warnings: MutableList<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
}
})
}
}
}
return mainView
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(START_KEY, startCalendar.timeInMillis)
outState.putLong(END_KEY, endCalendar.timeInMillis)
}
private fun openGpx(path: String) {
val fileUri = AndroidUtils.getUriForFile(app, File(path))
val openGpxIntent = Intent(Intent.ACTION_VIEW)
openGpxIntent.setDataAndType(fileUri, "application/gpx+xml")
// openGpxIntent.type = "application/gpx+xml"
openGpxIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val resolved = activity?.packageManager?.resolveActivity(openGpxIntent, PackageManager.MATCH_DEFAULT_ONLY)
if (resolved != null) {
startActivity(openGpxIntent)
}
}
private fun saveCurrentGpxToFile(listener: SavingTracksDbHelper.SaveGpxListener) {
app.savingTracksDbHelper.saveGpx(listener, app.getExternalFilesDir(null), gpxFile)
}
private fun readFromBundle(bundle: Bundle?) {
bundle?.also {
startCalendar.timeInMillis = it.getLong(START_KEY)
endCalendar.timeInMillis = it.getLong(END_KEY)
}
}
private fun setupBtnTextColor(textView: TextView) {
textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light))
}
private fun updateGpxInfo() {
gpxFile = app.savingTracksDbHelper.collectRecordedDataForUser(gpxFile.userId, gpxFile.chatId, startCalendar.timeInMillis, endCalendar.timeInMillis)
updateGPXStatisticRow()
updateDateAndTimeButtons()
updateGPXMap()
}
private fun updateDateAndTimeButtons() {
dateStartBtn.text = SimpleDateFormat("dd MMM", Locale.getDefault()).format(startCalendar.timeInMillis)
dateEndBtn.text = SimpleDateFormat("dd MMM", Locale.getDefault()).format(endCalendar.timeInMillis)
timeStartBtn.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(startCalendar.timeInMillis)
timeEndBtn.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(endCalendar.timeInMillis)
}
private fun updateGPXStatisticRow() {
val analysis: GPXUtilities.GPXTrackAnalysis = gpxFile.getAnalysis(0)
avgElevationTv.text = OsmandFormatter.getFormattedAlt(analysis.avgElevation, app)
avgSpeedTv.text = if (analysis.isSpeedSpecified) OsmandFormatter.getFormattedSpeed(analysis.avgSpeed, app) else ""
totalDistanceTv.text = OsmandFormatter.getFormattedDistance(analysis.totalDistance, app)
timeSpanTv.text = Algorithms.formatDuration((analysis.timeSpan / 1000).toInt(), true)
}
private fun updateGPXMap() {
saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener {
override fun onSavingGpxFinish(path: String) {
val mgr = activity?.getSystemService(Context.WINDOW_SERVICE)
if (mgr != null) {
val dm = DisplayMetrics()
(mgr as WindowManager).defaultDisplay.getMetrics(dm)
val widthPixels = dm.widthPixels - (2 * app.resources.getDimensionPixelSize(R.dimen.content_padding_standard))
val heightPixels = AndroidUtils.dpToPx(app, 152f)
val gpxUri = AndroidUtils.getUriForFile(app, File(path))
app.osmandAidlHelper.getBitmapForGpx(gpxUri, dm.density , widthPixels, heightPixels, GPX_TRACK_COLOR)
}
}
override fun onSavingGpxError(warnings: MutableList<String>?) {
log.debug("onSavingGpxError ${warnings?.firstOrNull()}")
}
})
}
private fun selectStartDate() {
val dateFromDialog =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
startCalendar.set(Calendar.YEAR, year)
startCalendar.set(Calendar.MONTH, monthOfYear)
startCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
updateGpxInfo()
}
DatePickerDialog(context, dateFromDialog,
startCalendar.get(Calendar.YEAR),
startCalendar.get(Calendar.MONTH),
startCalendar.get(Calendar.DAY_OF_MONTH)).show()
}
private fun selectStartTime() {
TimePickerDialog(context,
TimePickerDialog.OnTimeSetListener { _, hours, minutes ->
startCalendar.set(Calendar.HOUR_OF_DAY, hours)
startCalendar.set(Calendar.MINUTE, minutes)
updateGpxInfo()
}, 0, 0, true).show()
}
private fun selectEndDate() {
val dateFromDialog =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
endCalendar.set(Calendar.YEAR, year)
endCalendar.set(Calendar.MONTH, monthOfYear)
endCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
updateGpxInfo()
}
DatePickerDialog(context, dateFromDialog,
endCalendar.get(Calendar.YEAR),
endCalendar.get(Calendar.MONTH),
endCalendar.get(Calendar.DAY_OF_MONTH)).show()
}
private fun selectEndTime() {
TimePickerDialog(context,
TimePickerDialog.OnTimeSetListener { _, hours, minutes ->
endCalendar.set(Calendar.HOUR_OF_DAY, hours)
endCalendar.set(Calendar.MINUTE, minutes)
updateGpxInfo()
}, 0, 0, true).show()
}
companion object {
private const val TAG = "UserGpxInfoFragment"
private const val START_KEY = "start_key"
private const val END_KEY = "end_key"
private const val GPX_TRACK_COLOR = -65536
fun showInstance(fm: FragmentManager, gpxFile: GPXUtilities.GPXFile, start: Long, end: Long): Boolean {
return try {
val fragment = UserGpxInfoFragment().apply {
arguments = Bundle().apply {
putLong(START_KEY, start)
putLong(END_KEY, end)
}
}
fragment.gpxFile = gpxFile
fragment.show(fm, TAG)
true
} catch (e: RuntimeException) {
false
}
}
}
}

View file

@ -10,6 +10,7 @@ import android.text.TextUtils;
import net.osmand.Location; import net.osmand.Location;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox; import net.osmand.data.RotatedTileBox;
import net.osmand.telegram.TelegramApplication; import net.osmand.telegram.TelegramApplication;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -49,7 +50,7 @@ import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.util.TimeZone; import java.util.TimeZone;
// copy from net.osmand.plus.GPXUtilities and changes done to WptPt (userId,chatId) // copy from net.osmand.plus.GPXUtilities and changes done to WptPt and GPXFile (userId,chatId)
public class GPXUtilities { public class GPXUtilities {
public final static Log log = PlatformUtil.getLog(GPXUtilities.class); public final static Log log = PlatformUtil.getLog(GPXUtilities.class);
@ -815,6 +816,8 @@ public class GPXUtilities {
public String path = ""; public String path = "";
public boolean showCurrentTrack; public boolean showCurrentTrack;
public long modifiedTime = 0; public long modifiedTime = 0;
public int userId;
public long chatId;
private Track generalTrack; private Track generalTrack;
private TrkSegment generalSegment; private TrkSegment generalSegment;
@ -1248,6 +1251,57 @@ public class GPXUtilities {
} }
return categories; return categories;
} }
public QuadRect getRect() {
double left = 0, right = 0;
double top = 0, bottom = 0;
for (Track track : tracks) {
for (TrkSegment segment : track.segments) {
for (WptPt p : segment.points) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
}
}
for (WptPt p : points) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
for (GPXUtilities.Route route : routes) {
for (WptPt p : route.points) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
}
return new QuadRect(left, top, right, bottom);
}
} }
public static String asString(GPXFile file, TelegramApplication ctx) { public static String asString(GPXFile file, TelegramApplication ctx) {

View file

@ -23,6 +23,7 @@ object OsmandFormatter {
private const val SHORT_TIME_FORMAT = "%02d:%02d" private const val SHORT_TIME_FORMAT = "%02d:%02d"
private const val SIMPLE_TIME_OF_DAY_FORMAT = "HH:mm" private const val SIMPLE_TIME_OF_DAY_FORMAT = "HH:mm"
private const val SIMPLE_DATE_FORMAT = "dd MMM HH:mm:ss" private const val SIMPLE_DATE_FORMAT = "dd MMM HH:mm:ss"
private const val SHORT_DATE_FORMAT = "dd MMM yyyy"
private const val MIN_DURATION_FOR_DATE_FORMAT = 48 * 60 * 60 private const val MIN_DURATION_FOR_DATE_FORMAT = 48 * 60 * 60
@ -77,8 +78,13 @@ object OsmandFormatter {
} }
} }
fun getFormattedDate(seconds: Long): String = fun getFormattedDate(seconds: Long, shortFormat: Boolean = false): String {
SimpleDateFormat(SIMPLE_DATE_FORMAT, Locale.getDefault()).format(seconds * 1000L) return if (shortFormat) {
SimpleDateFormat(SIMPLE_DATE_FORMAT, Locale.getDefault()).format(seconds * 1000L)
} else {
SimpleDateFormat(SHORT_DATE_FORMAT, Locale.getDefault()).format(seconds * 1000L)
}
}
fun getListItemLiveTimeDescr(ctx: TelegramApplication, lastUpdated: Int, prefix: String = ""): String { fun getListItemLiveTimeDescr(ctx: TelegramApplication, lastUpdated: Int, prefix: String = ""): String {
return if (lastUpdated > 0) { return if (lastUpdated > 0) {

View file

@ -1,6 +1,7 @@
package net.osmand.aidl; package net.osmand.aidl;
import net.osmand.aidl.search.SearchResult; import net.osmand.aidl.search.SearchResult;
import net.osmand.aidl.gpx.AGpxBitmap;
interface IOsmAndAidlCallback { interface IOsmAndAidlCallback {
void onSearchComplete(in List<SearchResult> resultSet); void onSearchComplete(in List<SearchResult> resultSet);
@ -8,4 +9,6 @@ interface IOsmAndAidlCallback {
void onUpdate(); void onUpdate();
void onAppInitialized(); void onAppInitialized();
void onGpxBitmapCreated(in AGpxBitmap bitmap);
} }

View file

@ -74,6 +74,8 @@ import net.osmand.aidl.customization.OsmandSettingsParams;
import net.osmand.aidl.gpx.AGpxFile; import net.osmand.aidl.gpx.AGpxFile;
import net.osmand.aidl.gpx.AGpxFileDetails; import net.osmand.aidl.gpx.AGpxFileDetails;
import net.osmand.aidl.gpx.CreateGpxBitmapParams;
import net.osmand.aidl.tiles.ASqliteDbFile; import net.osmand.aidl.tiles.ASqliteDbFile;
import net.osmand.aidl.plugins.PluginParams; import net.osmand.aidl.plugins.PluginParams;
@ -175,4 +177,6 @@ interface IOsmAndAidlInterface {
boolean changePluginState(in PluginParams params); boolean changePluginState(in PluginParams params);
boolean registerForOsmandInitListener(in IOsmAndAidlCallback callback); boolean registerForOsmandInitListener(in IOsmAndAidlCallback callback);
boolean getBitmapForGpx(in CreateGpxBitmapParams file, IOsmAndAidlCallback callback);
} }

View file

@ -10,6 +10,7 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -24,10 +25,12 @@ import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import net.osmand.CallbackWithObject;
import net.osmand.IndexConstants; import net.osmand.IndexConstants;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.aidl.favorite.AFavorite; import net.osmand.aidl.favorite.AFavorite;
import net.osmand.aidl.favorite.group.AFavoriteGroup; import net.osmand.aidl.favorite.group.AFavoriteGroup;
import net.osmand.aidl.gpx.AGpxBitmap;
import net.osmand.aidl.gpx.AGpxFile; import net.osmand.aidl.gpx.AGpxFile;
import net.osmand.aidl.gpx.AGpxFileDetails; import net.osmand.aidl.gpx.AGpxFileDetails;
import net.osmand.aidl.gpx.ASelectedGpxFile; import net.osmand.aidl.gpx.ASelectedGpxFile;
@ -69,6 +72,7 @@ import net.osmand.plus.dialogs.ConfigureMapMenu;
import net.osmand.plus.helpers.ColorDialogs; import net.osmand.plus.helpers.ColorDialogs;
import net.osmand.plus.helpers.ExternalApiHelper; import net.osmand.plus.helpers.ExternalApiHelper;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.myplaces.TrackBitmapDrawer;
import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.views.AidlMapLayer; import net.osmand.plus.views.AidlMapLayer;
@ -1907,7 +1911,96 @@ public class OsmandAidlApi {
return app.getAppCustomization().changePluginStatus(params); return app.getAppCustomization().changePluginStatus(params);
} }
boolean getBitmapForGpx(final Uri gpxUri, final float density, final int widthPixels, final int heightPixels, final int color, final GpxBitmapCreatedCallback callback) {
if (gpxUri == null || callback == null) {
return false;
}
final TrackBitmapDrawer.TrackBitmapDrawerListener drawerListener = new TrackBitmapDrawer.TrackBitmapDrawerListener() {
@Override
public void onTrackBitmapDrawing() {
}
@Override
public void onTrackBitmapDrawn() {
}
@Override
public boolean isTrackBitmapSelectionSupported() {
return false;
}
@Override
public void drawTrackBitmap(Bitmap bitmap) {
callback.onGpxBitmapCreatedComplete(new AGpxBitmap(bitmap));
}
};
if (app.isApplicationInitializing()) {
app.getAppInitializer().addListener(new AppInitializer.AppInitializeListener() {
@Override
public void onProgress(AppInitializer init, AppInitializer.InitEvents event) {
}
@Override
public void onFinish(AppInitializer init) {
createGpxBitmapFromUri(gpxUri, density, widthPixels, heightPixels, color, drawerListener);
}
});
} else {
createGpxBitmapFromUri(gpxUri, density, widthPixels, heightPixels, color, drawerListener);
}
return true;
}
private void createGpxBitmapFromUri(final Uri gpxUri, final float density, final int widthPixels, final int heightPixels, final int color, final TrackBitmapDrawer.TrackBitmapDrawerListener drawerListener) {
GpxAsyncLoaderTask gpxAsyncLoaderTask = new GpxAsyncLoaderTask(app, gpxUri, new CallbackWithObject<GPXFile>() {
@Override
public boolean processResult(GPXFile result) {
TrackBitmapDrawer trackBitmapDrawer = new TrackBitmapDrawer(app, result, null, result.getRect(), density, widthPixels, heightPixels);
trackBitmapDrawer.addListener(drawerListener);
trackBitmapDrawer.setDrawEnabled(true);
trackBitmapDrawer.setTrackColor(color);
trackBitmapDrawer.initAndDraw();
return false;
}
});
gpxAsyncLoaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private static class GpxAsyncLoaderTask extends AsyncTask<Void, Void, GPXFile> {
private final OsmandApplication app;
private final CallbackWithObject<GPXFile> callback;
private final Uri gpxUri;
GpxAsyncLoaderTask(@NonNull OsmandApplication app, @NonNull Uri gpxUri, final CallbackWithObject<GPXFile> callback) {
this.app = app;
this.gpxUri = gpxUri;
this.callback = callback;
}
@Override
protected void onPostExecute(GPXFile gpxFile) {
if (gpxFile.warning == null && callback != null) {
callback.processResult(gpxFile);
}
}
@Override
protected GPXFile doInBackground(Void... voids) {
ParcelFileDescriptor gpxParcelDescriptor = null;
try {
gpxParcelDescriptor = app.getContentResolver().openFileDescriptor(gpxUri, "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (gpxParcelDescriptor != null) {
final FileDescriptor fileDescriptor = gpxParcelDescriptor.getFileDescriptor();
return GPXUtilities.loadGPXFile(app, new FileInputStream(fileDescriptor));
}
return null;
}
}
private static AGpxFileDetails createGpxFileDetails(@NonNull GPXTrackAnalysis a) { private static AGpxFileDetails createGpxFileDetails(@NonNull GPXTrackAnalysis a) {
return new AGpxFileDetails(a.totalDistance, a.totalTracks, a.startTime, a.endTime, return new AGpxFileDetails(a.totalDistance, a.totalTracks, a.startTime, a.endTime,
@ -1980,6 +2073,10 @@ public class OsmandAidlApi {
void onSearchComplete(List<SearchResult> resultSet); void onSearchComplete(List<SearchResult> resultSet);
} }
public interface GpxBitmapCreatedCallback {
void onGpxBitmapCreatedComplete(AGpxBitmap aGpxBitmap);
}
public interface OsmandAppInitCallback { public interface OsmandAppInitCallback {
void onAppInitialized(); void onAppInitialized();
} }

View file

@ -11,6 +11,7 @@ import android.os.RemoteException;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.aidl.OsmandAidlApi.GpxBitmapCreatedCallback;
import net.osmand.aidl.OsmandAidlApi.OsmandAppInitCallback; import net.osmand.aidl.OsmandAidlApi.OsmandAppInitCallback;
import net.osmand.aidl.OsmandAidlApi.SearchCompleteCallback; import net.osmand.aidl.OsmandAidlApi.SearchCompleteCallback;
import net.osmand.aidl.calculateroute.CalculateRouteParams; import net.osmand.aidl.calculateroute.CalculateRouteParams;
@ -22,8 +23,10 @@ import net.osmand.aidl.favorite.UpdateFavoriteParams;
import net.osmand.aidl.favorite.group.AddFavoriteGroupParams; import net.osmand.aidl.favorite.group.AddFavoriteGroupParams;
import net.osmand.aidl.favorite.group.RemoveFavoriteGroupParams; import net.osmand.aidl.favorite.group.RemoveFavoriteGroupParams;
import net.osmand.aidl.favorite.group.UpdateFavoriteGroupParams; import net.osmand.aidl.favorite.group.UpdateFavoriteGroupParams;
import net.osmand.aidl.gpx.AGpxBitmap;
import net.osmand.aidl.gpx.AGpxFile; import net.osmand.aidl.gpx.AGpxFile;
import net.osmand.aidl.gpx.ASelectedGpxFile; import net.osmand.aidl.gpx.ASelectedGpxFile;
import net.osmand.aidl.gpx.CreateGpxBitmapParams;
import net.osmand.aidl.gpx.HideGpxParams; import net.osmand.aidl.gpx.HideGpxParams;
import net.osmand.aidl.gpx.ImportGpxParams; import net.osmand.aidl.gpx.ImportGpxParams;
import net.osmand.aidl.gpx.RemoveGpxParams; import net.osmand.aidl.gpx.RemoveGpxParams;
@ -822,5 +825,25 @@ public class OsmandAidlService extends Service {
return false; return false;
} }
} }
@Override
public boolean getBitmapForGpx(CreateGpxBitmapParams params, final IOsmAndAidlCallback callback) throws RemoteException {
try {
OsmandAidlApi api = getApi("getBitmapForGpx");
return params != null && api != null && api.getBitmapForGpx(params.getGpxUri(), params.getDensity(), params.getWidthPixels(), params.getHeightPixels(), params.getColor(), new GpxBitmapCreatedCallback() {
@Override
public void onGpxBitmapCreatedComplete(AGpxBitmap aGpxBitmap) {
try {
callback.onGpxBitmapCreated(aGpxBitmap);
} catch (RemoteException e) {
handleException(e);
}
}
});
} catch (Exception e) {
handleException(e);
return false;
}
}
}; };
} }

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.gpx;
parcelable AGpxBitmap;

View file

@ -0,0 +1,46 @@
package net.osmand.aidl.gpx;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
public class AGpxBitmap implements Parcelable {
private Bitmap bitmap;
public AGpxBitmap(@NonNull Bitmap bitmap) {
this.bitmap = bitmap;
}
public AGpxBitmap(Parcel in) {
readFromParcel(in);
}
public Bitmap getBitmap() {
return bitmap;
}
public static final Creator<AGpxBitmap> CREATOR = new
Creator<AGpxBitmap>() {
public AGpxBitmap createFromParcel(Parcel in) {
return new AGpxBitmap(in);
}
public AGpxBitmap[] newArray(int size) {
return new AGpxBitmap[size];
}
};
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(bitmap, flags);
}
private void readFromParcel(Parcel in) {
bitmap = in.readParcelable(Bitmap.class.getClassLoader());
}
public int describeContents() {
return 0;
}
}

View file

@ -0,0 +1,3 @@
package net.osmand.aidl.gpx;
parcelable CreateGpxBitmapParams;

View file

@ -0,0 +1,101 @@
package net.osmand.aidl.gpx;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.File;
public class CreateGpxBitmapParams implements Parcelable {
private File gpxFile;
private Uri gpxUri;
private float density;
private int widthPixels;
private int heightPixels;
private int color; //ARGB color int
public CreateGpxBitmapParams(File gpxFile, float density, int widthPixels, int heightPixels, int color) {
this.gpxFile = gpxFile;
this.density = density;
this.widthPixels = widthPixels;
this.heightPixels = heightPixels;
this.color = color;
}
public CreateGpxBitmapParams(Uri gpxUri, float density, int widthPixels, int heightPixels, int color) {
this.gpxUri = gpxUri;
this.density = density;
this.widthPixels = widthPixels;
this.heightPixels = heightPixels;
this.color = color;
}
public CreateGpxBitmapParams(Parcel in) {
readFromParcel(in);
}
public static final Creator<CreateGpxBitmapParams> CREATOR = new
Creator<CreateGpxBitmapParams>() {
public CreateGpxBitmapParams createFromParcel(Parcel in) {
return new CreateGpxBitmapParams(in);
}
public CreateGpxBitmapParams[] newArray(int size) {
return new CreateGpxBitmapParams[size];
}
};
public File getGpxFile() {
return gpxFile;
}
public Uri getGpxUri() {
return gpxUri;
}
public int getWidthPixels() {
return widthPixels;
}
public int getHeightPixels() {
return heightPixels;
}
public float getDensity() {
return density;
}
public int getColor() {
return color;
}
public void writeToParcel(Parcel out, int flags) {
if (gpxFile != null) {
out.writeString(gpxFile.getAbsolutePath());
} else {
out.writeString(null);
}
out.writeParcelable(gpxUri, flags);
out.writeFloat(density);
out.writeInt(widthPixels);
out.writeInt(heightPixels);
out.writeInt(color);
}
private void readFromParcel(Parcel in) {
String gpxAbsolutePath = in.readString();
if (gpxAbsolutePath != null) {
gpxFile = new File(gpxAbsolutePath);
}
gpxUri = in.readParcelable(Uri.class.getClassLoader());
density = in.readFloat();
widthPixels = in.readInt();
heightPixels = in.readInt();
color = in.readInt();
}
public int describeContents() {
return 0;
}
}

View file

@ -13,6 +13,7 @@ import net.osmand.Location;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.data.LocationPoint; import net.osmand.data.LocationPoint;
import net.osmand.data.PointDescription; import net.osmand.data.PointDescription;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox; import net.osmand.data.RotatedTileBox;
import net.osmand.plus.views.Renderable; import net.osmand.plus.views.Renderable;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -1247,6 +1248,57 @@ public class GPXUtilities {
} }
return categories; return categories;
} }
public QuadRect getRect() {
double left = 0, right = 0;
double top = 0, bottom = 0;
for (Track track : tracks) {
for (TrkSegment segment : track.segments) {
for (WptPt p : segment.points) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
}
}
for (WptPt p : points) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
for (GPXUtilities.Route route : routes) {
for (WptPt p : route.points) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
}
return new QuadRect(left, top, right, bottom);
}
} }
public static String asString(GPXFile file, OsmandApplication ctx) { public static String asString(GPXFile file, OsmandApplication ctx) {

View file

@ -1,5 +1,6 @@
package net.osmand.plus.activities; package net.osmand.plus.activities;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -9,9 +10,11 @@ import android.support.annotation.Nullable;
import android.support.design.widget.BottomNavigationView; import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
@ -20,7 +23,6 @@ import net.osmand.data.QuadRect;
import net.osmand.plus.GPXDatabase.GpxDataItem; import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GPXUtilities; import net.osmand.plus.GPXUtilities;
import net.osmand.plus.GPXUtilities.GPXFile; import net.osmand.plus.GPXUtilities.GPXFile;
import net.osmand.plus.GPXUtilities.Track;
import net.osmand.plus.GPXUtilities.TrkSegment; import net.osmand.plus.GPXUtilities.TrkSegment;
import net.osmand.plus.GPXUtilities.WptPt; import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.GpxSelectionHelper; import net.osmand.plus.GpxSelectionHelper;
@ -35,7 +37,6 @@ import net.osmand.plus.mapmarkers.CoordinateInputDialogFragment;
import net.osmand.plus.measurementtool.NewGpxData; import net.osmand.plus.measurementtool.NewGpxData;
import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.myplaces.FavoritesActivity;
import net.osmand.plus.myplaces.SplitSegmentDialogFragment; import net.osmand.plus.myplaces.SplitSegmentDialogFragment;
import net.osmand.plus.myplaces.TrackActivityFragmentAdapter;
import net.osmand.plus.myplaces.TrackBitmapDrawer; import net.osmand.plus.myplaces.TrackBitmapDrawer;
import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener; import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener;
import net.osmand.plus.myplaces.TrackPointFragment; import net.osmand.plus.myplaces.TrackPointFragment;
@ -156,56 +157,11 @@ public class TrackActivity extends TabActivity {
} }
public QuadRect getRect() { public QuadRect getRect() {
double left = 0, right = 0;
double top = 0, bottom = 0;
if (getGpx() != null) { if (getGpx() != null) {
for (Track track : getGpx().tracks) { return getGpx().getRect();
for (TrkSegment segment : track.segments) { } else {
for (WptPt p : segment.points) { return new QuadRect(0, 0, 0, 0);
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
}
}
for (WptPt p : getGpx().getPoints()) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
for (GPXUtilities.Route route : getGpx().routes) {
for (WptPt p : route.points) {
if (left == 0 && right == 0) {
left = p.getLongitude();
right = p.getLongitude();
top = p.getLatitude();
bottom = p.getLatitude();
} else {
left = Math.min(left, p.getLongitude());
right = Math.max(right, p.getLongitude());
top = Math.max(top, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
}
}
}
} }
return new QuadRect(left, top, right, bottom);
} }
protected void setGpxDataItem(GpxDataItem gpxDataItem) { protected void setGpxDataItem(GpxDataItem gpxDataItem) {
@ -383,8 +339,11 @@ public class TrackActivity extends TabActivity {
setGpx(gpxFile); setGpx(gpxFile);
setGpxDataItem(file != null ? app.getGpxDatabase().getItem(file) : null); setGpxDataItem(file != null ? app.getGpxDatabase().getItem(file) : null);
if (gpxFile != null) { WindowManager mgr = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
trackBitmapDrawer = new TrackBitmapDrawer(this, gpxFile, getGpxDataItem(), getRect()); if (gpxFile != null && mgr != null) {
DisplayMetrics dm = new DisplayMetrics();
mgr.getDefaultDisplay().getMetrics(dm);
trackBitmapDrawer = new TrackBitmapDrawer(app, gpxFile, getGpxDataItem(), getRect(), dm.density, dm.widthPixels, AndroidUtils.dpToPx(app, 152f));
} }
for (WeakReference<Fragment> f : fragList) { for (WeakReference<Fragment> f : fragList) {

View file

@ -1,7 +1,5 @@
package net.osmand.plus.myplaces; package net.osmand.plus.myplaces;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -12,8 +10,6 @@ import android.support.annotation.ColorInt;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
@ -32,15 +28,16 @@ import net.osmand.plus.resources.ResourceManager;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.Renderable; import net.osmand.plus.views.Renderable;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class TrackBitmapDrawer { public class TrackBitmapDrawer {
private OsmandApplication app; private OsmandApplication app;
private WeakReference<Activity> activityRef;
private QuadRect rect; private QuadRect rect;
private float density;
private int widthPixels;
private int heightPixels;
private boolean drawEnabled; private boolean drawEnabled;
private RotatedTileBox rotatedTileBox; private RotatedTileBox rotatedTileBox;
@ -69,11 +66,13 @@ public class TrackBitmapDrawer {
void drawTrackBitmap(Bitmap bitmap); void drawTrackBitmap(Bitmap bitmap);
} }
public TrackBitmapDrawer(@NonNull Activity activity, @NonNull GPXFile gpxFile, public TrackBitmapDrawer(@NonNull OsmandApplication app, @NonNull GPXFile gpxFile,
@Nullable GpxDataItem gpxDataItem, @NonNull QuadRect rect) { @Nullable GpxDataItem gpxDataItem, @NonNull QuadRect rect, float density, int widthPixels, int heightPixels) {
this.activityRef = new WeakReference<>(activity); this.density = density;
this.widthPixels = widthPixels;
this.heightPixels = heightPixels;
this.rect = rect; this.rect = rect;
this.app = (OsmandApplication) activity.getApplication(); this.app = app;
this.gpxFile = gpxFile; this.gpxFile = gpxFile;
this.gpxDataItem = gpxDataItem; this.gpxDataItem = gpxDataItem;
@ -83,8 +82,8 @@ public class TrackBitmapDrawer {
paint.setStrokeWidth(AndroidUtils.dpToPx(app, 4f)); paint.setStrokeWidth(AndroidUtils.dpToPx(app, 4f));
defPointColor = ContextCompat.getColor(app, R.color.gpx_color_point); defPointColor = ContextCompat.getColor(app, R.color.gpx_color_point);
paintIcon = new Paint(); paintIcon = new Paint();
pointSmall = BitmapFactory.decodeResource(activity.getResources(), R.drawable.map_white_shield_small); pointSmall = BitmapFactory.decodeResource(app.getResources(), R.drawable.map_white_shield_small);
selectedPoint = BitmapFactory.decodeResource(activity.getResources(), R.drawable.map_default_location); selectedPoint = BitmapFactory.decodeResource(app.getResources(), R.drawable.map_default_location);
} }
public void addListener(TrackBitmapDrawerListener l) { public void addListener(TrackBitmapDrawerListener l) {
@ -150,54 +149,48 @@ public class TrackBitmapDrawer {
} }
public boolean initAndDraw() { public boolean initAndDraw() {
Activity activity = activityRef.get(); if (rect != null && rect.left != 0 && rect.top != 0) {
if (activity != null) { notifyDrawing();
WindowManager mgr = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
if (mgr != null && rect != null && rect.left != 0 && rect.top != 0) {
notifyDrawing();
double clat = rect.bottom / 2 + rect.top / 2; double clat = rect.bottom / 2 + rect.top / 2;
double clon = rect.left / 2 + rect.right / 2; double clon = rect.left / 2 + rect.right / 2;
DisplayMetrics dm = new DisplayMetrics(); RotatedTileBox.RotatedTileBoxBuilder boxBuilder = new RotatedTileBox.RotatedTileBoxBuilder()
mgr.getDefaultDisplay().getMetrics(dm); .setLocation(clat, clon)
RotatedTileBox.RotatedTileBoxBuilder boxBuilder = new RotatedTileBox.RotatedTileBoxBuilder() .setZoom(15)
.setLocation(clat, clon) .density(density)
.setZoom(15) .setPixelDimensions(widthPixels, heightPixels, 0.5f, 0.5f);
.density(dm.density)
.setPixelDimensions(dm.widthPixels, AndroidUtils.dpToPx(app, 152f), 0.5f, 0.5f);
rotatedTileBox = boxBuilder.build(); rotatedTileBox = boxBuilder.build();
while (rotatedTileBox.getZoom() < 17 && rotatedTileBox.containsLatLon(rect.top, rect.left) && rotatedTileBox.containsLatLon(rect.bottom, rect.right)) { while (rotatedTileBox.getZoom() < 17 && rotatedTileBox.containsLatLon(rect.top, rect.left) && rotatedTileBox.containsLatLon(rect.bottom, rect.right)) {
rotatedTileBox.setZoom(rotatedTileBox.getZoom() + 1); rotatedTileBox.setZoom(rotatedTileBox.getZoom() + 1);
} }
while (rotatedTileBox.getZoom() >= 7 && (!rotatedTileBox.containsLatLon(rect.top, rect.left) || !rotatedTileBox.containsLatLon(rect.bottom, rect.right))) { while (rotatedTileBox.getZoom() >= 7 && (!rotatedTileBox.containsLatLon(rect.top, rect.left) || !rotatedTileBox.containsLatLon(rect.bottom, rect.right))) {
rotatedTileBox.setZoom(rotatedTileBox.getZoom() - 1); rotatedTileBox.setZoom(rotatedTileBox.getZoom() - 1);
} }
final DrawSettings drawSettings = new DrawSettings(!app.getSettings().isLightContent(), true); final DrawSettings drawSettings = new DrawSettings(!app.getSettings().isLightContent(), true);
final ResourceManager resourceManager = app.getResourceManager(); final ResourceManager resourceManager = app.getResourceManager();
final MapRenderRepositories renderer = resourceManager.getRenderer(); final MapRenderRepositories renderer = resourceManager.getRenderer();
if (resourceManager.updateRenderedMapNeeded(rotatedTileBox, drawSettings)) { if (resourceManager.updateRenderedMapNeeded(rotatedTileBox, drawSettings)) {
resourceManager.updateRendererMap(rotatedTileBox, new OnMapLoadedListener() { resourceManager.updateRendererMap(rotatedTileBox, new OnMapLoadedListener() {
@Override @Override
public void onMapLoaded(boolean interrupted) { public void onMapLoaded(boolean interrupted) {
app.runInUIThread(new Runnable() { app.runInUIThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (isDrawEnabled()) { if (isDrawEnabled()) {
mapBitmap = renderer.getBitmap(); mapBitmap = renderer.getBitmap();
if (mapBitmap != null) { if (mapBitmap != null) {
notifyDrawn(); notifyDrawn();
refreshTrackBitmap(); refreshTrackBitmap();
}
} }
} }
}); }
} });
}); }
} });
return true;
} }
return true;
} }
return false; return false;
} }