[android] Initial implementation of search.

This commit is contained in:
vng 2012-06-15 23:33:50 -07:00 committed by Alex Zolotarev
parent 953cba146a
commit 04747a3f78
9 changed files with 676 additions and 0 deletions

View file

@ -50,5 +50,11 @@
android:noHistory="true"
android:configChanges="orientation">
</activity>
<activity android:name="com.mapswithme.maps.SearchActivity"
android:label="@string/search_maps"
android:screenOrientation="behind"
android:noHistory="true"
android:configChanges="orientation">
</activity>
</application>
</manifest>

View file

@ -34,6 +34,7 @@ LOCAL_SRC_FILES := \
com/mapswithme/maps/Lifecycle.cpp \
com/mapswithme/maps/MapStorage.cpp \
com/mapswithme/maps/DownloadResourcesActivity.cpp \
com/mapswithme/maps/SearchActivity.cpp \
com/mapswithme/platform/Platform.cpp \
com/mapswithme/platform/HttpThread.cpp \
com/mapswithme/platform/Language.cpp \

View file

@ -376,6 +376,17 @@ namespace android
m_work.ShowRect(r);
}
void Framework::ShowSearchResult(search::Result const & r)
{
m_doLoadState = false;
m_work.ShowSearchResult(r);
}
void Framework::Search(search::SearchParams const & params)
{
m_work.Search(params);
}
void Framework::LoadState()
{
if (!m_work.LoadState())

View file

@ -83,6 +83,9 @@ namespace android
/// Show rect from another activity. Ensure that no LoadState will be called,
/// when maim map activity will become active.
void ShowCountry(m2::RectD const & r);
void ShowSearchResult(search::Result const & r);
void Search(search::SearchParams const & params);
void LoadState();
void SaveState();

View file

@ -0,0 +1,225 @@
#include "Framework.hpp"
#include "../../../../../search/result.hpp"
#include "../../../../../map/measurement_utils.hpp"
#include "../../../../../base/thread.hpp"
#include "../core/jni_helper.hpp"
class SearchAdapter
{
/// @name Results holder. Store last valid results from search threads (m_storeID)
/// and current result to show in GUI (m_ID).
//@{
search::Results m_storeResults, m_results;
int m_storeID, m_ID;
//@}
threads::Mutex m_updateMutex;
/// Last saved activity to run update UI.
jobject m_activity;
// This function may be called several times for one queryID.
// In that case we should increment m_storeID to distinguish different results.
// Main queryID is incremented by 5-step to leave space for middle queries.
// This constant should be equal with SearchActivity.QUERY_STEP;
static int const QUERY_STEP = 5;
void OnResults(search::Results const & res, int queryID)
{
if (s_pInstance == 0)
{
// In case when activity is destroyed, but search thread passed any results.
return;
}
threads::MutexGuard guard(m_updateMutex);
// store current results
m_storeResults = res;
if (m_storeID >= queryID && m_storeID < queryID + QUERY_STEP)
{
++m_storeID;
// not more than QUERY_STEP results per query
ASSERT_LESS ( m_storeID, queryID + QUERY_STEP, () );
}
else
{
ASSERT_LESS ( m_storeID, queryID, () );
m_storeID = queryID;
}
// get new environment pointer here because of different thread
JNIEnv * env = jni::GetEnv();
// post message to update ListView in UI thread
jmethodID const id = jni::GetJavaMethodID(env, m_activity, "updateData", "(II)V");
env->CallVoidMethod(m_activity, id,
static_cast<jint>(m_storeResults.GetCount()),
static_cast<jint>(m_storeID));
}
bool AcquireShowResults(int resultID)
{
if (resultID != m_ID)
{
{
// Grab last results.
threads::MutexGuard guard(m_updateMutex);
m_results.Swap(m_storeResults);
m_ID = m_storeID;
}
if (resultID != m_ID)
{
// It happens only when better results came faster than GUI.
// It is a rare case, skip this query.
ASSERT_GREATER ( m_ID, resultID, () );
return false;
}
}
return true;
}
bool CheckPosition(int position) const
{
int const count = static_cast<int>(m_results.GetCount());
// for safety reasons do actual check always
ASSERT_LESS ( position, count, () );
return (position < count);
}
SearchAdapter(jobject activity) : m_ID(0), m_storeID(0), m_activity(activity)
{
}
static SearchAdapter * s_pInstance;
public:
/// @name Instance lifetime functions.
/// TODO May be we should increment/deincrement global reference for m_activity
//@{
static void CreateInstance(jobject activity)
{
ASSERT ( s_pInstance == 0, () );
s_pInstance = new SearchAdapter(activity);
}
static void DestroyInstance()
{
ASSERT ( s_pInstance, () );
delete s_pInstance;
s_pInstance = 0;
}
static SearchAdapter & Instance()
{
ASSERT ( s_pInstance, () );
return *s_pInstance;
}
//@}
void RunSearch(JNIEnv * env, search::SearchParams & params, int queryID)
{
params.m_callback = bind(&SearchAdapter::OnResults, this, _1, queryID);
g_framework->Search(params);
}
void ShowItem(int position)
{
if (CheckPosition(position))
g_framework->ShowSearchResult(m_results.GetResult(position));
}
search::Result const * GetResult(int position, int resultID)
{
if (AcquireShowResults(resultID) && CheckPosition(position))
return &(m_results.GetResult(position));
return 0;
}
};
SearchAdapter * SearchAdapter::s_pInstance = 0;
extern "C"
{
JNIEXPORT void JNICALL
Java_com_mapswithme_maps_SearchActivity_nativeInitSearch(JNIEnv * env, jobject thiz)
{
SearchAdapter::CreateInstance(thiz);
}
JNIEXPORT void JNICALL
Java_com_mapswithme_maps_SearchActivity_nativeFinishSearch(JNIEnv * env, jobject thiz)
{
SearchAdapter::DestroyInstance();
}
JNIEXPORT void JNICALL
Java_com_mapswithme_maps_SearchActivity_nativeRunSearch(
JNIEnv * env, jobject thiz,
jstring s, jstring lang, jdouble lat, jdouble lon, jint mode, jint queryID)
{
search::SearchParams params;
params.m_query = jni::ToNativeString(env, s);
params.SetInputLanguage(jni::ToNativeString(env, lang));
if (mode != 0)
params.SetPosition(lat, lon);
SearchAdapter::Instance().RunSearch(env, params, queryID);
}
JNIEXPORT void JNICALL
Java_com_mapswithme_maps_SearchActivity_nativeShowItem(JNIEnv * env, jobject thiz, jint position)
{
SearchAdapter::Instance().ShowItem(position);
}
JNIEXPORT jobject JNICALL
Java_com_mapswithme_maps_SearchActivity_nativeGetResult(
JNIEnv * env, jobject thiz, jint position, jint queryID)
{
search::Result const * res = SearchAdapter::Instance().GetResult(position, queryID);
if (res == 0) return 0;
jclass klass = env->FindClass("com/mapswithme/maps/SearchActivity$SearchAdapter$SearchResult");
ASSERT ( klass, () );
if (res->GetResultType() == search::Result::RESULT_FEATURE)
{
jmethodID methodID = env->GetMethodID(
klass, "<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
ASSERT ( methodID, () );
string distance;
double const d = res->GetDistanceFromCenter();
if (d >= 0.0)
CHECK ( MeasurementUtils::FormatDistance(d, distance), () );
return env->NewObject(klass, methodID,
jni::ToJavaString(env, res->GetString()),
jni::ToJavaString(env, res->GetRegionString()),
jni::ToJavaString(env, res->GetFeatureType()),
jni::ToJavaString(env, distance.c_str()),
jni::ToJavaString(env, res->GetRegionFlag()));
}
else
{
jmethodID methodID = env->GetMethodID(klass, "<init>", "(Ljava/lang/String;)V");
ASSERT ( methodID, () );
return env->NewObject(klass, methodID, jni::ToJavaString(env, res->GetSuggestionString()));
}
}
}

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingRight="?android:attr/scrollbarSize">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true">
<TextView
android:id="@+id/name"
android:text="Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:id="@+id/country"
android:text="Long Country Name xxxxxxxxxxxxxxxxxxxxxxxx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true">
<TextView
android:id="@+id/distance"
android:text="Long Distance Value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<TextView
android:id="@+id/amenity"
android:text="Amenity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
</RelativeLayout>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<EditText android:id="@+id/search_string"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>

View file

@ -262,6 +262,8 @@ public class MWMActivity extends NvEventQueueActivity implements LocationService
{
if (!nativeIsProVersion())
showProVersionBanner(getString(R.string.search_available_in_pro_version));
else
startActivity(new Intent(this, SearchActivity.class));
}
public void onDownloadClicked(View v)

View file

@ -0,0 +1,355 @@
package com.mapswithme.maps;
import java.util.Locale;
import android.app.Activity;
import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import com.mapswithme.maps.location.LocationService;
public class SearchActivity extends ListActivity implements LocationService.Listener
{
private static String TAG = "SearchActivity";
private static class SearchAdapter extends BaseAdapter
{
private Activity m_context;
private LayoutInflater m_inflater;
int m_count = 0;
int m_resultID = 0;
public SearchAdapter(Activity context)
{
m_context = context;
m_inflater = (LayoutInflater) m_context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getItemViewType(int position)
{
return 0;
}
@Override
public int getViewTypeCount()
{
return 1;
}
@Override
public int getCount()
{
return m_count;
}
@Override
public Object getItem(int position)
{
return position;
}
@Override
public long getItemId(int position)
{
return position;
}
private static class ViewHolder
{
public TextView m_name = null;
public TextView m_country = null;
public TextView m_distance = null;
public TextView m_amenity = null;
void initFromView(View v)
{
m_name = (TextView) v.findViewById(R.id.name);
m_country = (TextView) v.findViewById(R.id.country);
m_distance = (TextView) v.findViewById(R.id.distance);
m_amenity = (TextView) v.findViewById(R.id.amenity);
}
}
/// Created from native code.
public static class SearchResult
{
public String m_name;
public String m_country;
public String m_amenity;
public String m_distance;
public String m_flag;
/// 0 - suggestion result
/// 1 - feature result
public int m_type;
public SearchResult(String suggestion)
{
m_name = suggestion;
m_type = 0;
}
public SearchResult(String name, String country, String amenity,
String distance, String flag)
{
m_name = name;
m_country = country;
m_amenity = amenity;
m_distance = distance;
m_flag = flag;
m_type = 1;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
switch (getItemViewType(position))
{
case 0:
convertView = m_inflater.inflate(R.layout.search_item, null);
holder.initFromView(convertView);
break;
}
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
final SearchResult r = SearchActivity.nativeGetResult(position, m_resultID);
if (r != null)
{
holder.m_name.setText(r.m_name);
holder.m_country.setText(r.m_country);
holder.m_amenity.setText(r.m_amenity);
holder.m_distance.setText(r.m_distance);
}
return convertView;
}
/// Update list data.
public void updateData(int count, int resultID)
{
m_count = count;
m_resultID = resultID;
notifyDataSetChanged();
}
/// Show tapped country or get suggestion.
public String showCountry(int position)
{
final SearchResult r = SearchActivity.nativeGetResult(position, m_resultID);
if (r != null)
{
if (r.m_type == 1)
{
// show country and close activity
SearchActivity.nativeShowItem(position);
return null;
}
else
{
// advise suggestion
return r.m_name;
}
}
// return an empty string as a suggestion
return "";
}
}
private EditText getSearchBox()
{
return (EditText) findViewById(R.id.search_string);
}
private String getSearchString()
{
final String s = getSearchBox().getText().toString();
Log.d(TAG, "Search string = " + s);
return s;
}
private LocationService m_location;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
nativeInitSearch();
m_location = ((MWMApplication) getApplication()).getLocationService();
setContentView(R.layout.search_list_view);
EditText v = getSearchBox();
v.addTextChangedListener(new TextWatcher()
{
@Override
public void afterTextChanged(Editable s)
{
runSearch();
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3)
{
}
@Override
public void onTextChanged(CharSequence s, int arg1, int arg2, int arg3)
{
}
});
setListAdapter(new SearchAdapter(this));
}
@Override
protected void onDestroy()
{
super.onDestroy();
nativeFinishSearch();
}
@Override
protected void onResume()
{
super.onResume();
m_location.startUpdate(this);
// do the search immediately after resume
runSearch();
}
@Override
protected void onPause()
{
super.onPause();
m_location.stopUpdate(this);
}
private SearchAdapter getSA()
{
return (SearchAdapter) getListView().getAdapter();
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
final String suggestion = getSA().showCountry(position);
if (suggestion == null)
{
// close activity
finish();
}
else
{
// set suggestion string and run search
getSearchBox().setText(suggestion);
runSearch();
}
}
/// Current position.
private double m_lat;
private double m_lon;
/// It's should be equal to search::SearchParams::ModeT
/// Now it's just a flag to ensure that current position exists (!= 0).
int m_mode = 0;
@Override
public void onLocationUpdated(long time, double lat, double lon, float accuracy)
{
m_mode = 1;
m_lat = lat;
m_lon = lon;
runSearch();
}
@Override
public void onCompassUpdated(long time, double magneticNorth, double trueNorth, double accuracy)
{
}
@Override
public void onLocationStatusChanged(int status)
{
}
private int m_queryID = 0;
/// Make 5-step increment to leave space for middle queries.
/// This constant should be equal with native SearchAdapter::QUERY_STEP;
private final static int QUERY_STEP = 5;
public void updateData(final int count, final int resultID)
{
runOnUiThread(new Runnable()
{
@Override
public void run()
{
// emit only last query
if (resultID >= m_queryID && resultID < m_queryID + QUERY_STEP)
{
Log.d(TAG, "Show " + count + " results for id = " + resultID);
getSA().updateData(count, resultID);
}
}
});
}
private void runSearch()
{
// TODO Need to get input language
final String lang = Locale.getDefault().getLanguage();
Log.d(TAG, "Current language = " + lang);
m_queryID += QUERY_STEP;
nativeRunSearch(getSearchString(), lang, m_lat, m_lon, m_mode, m_queryID);
}
private native void nativeInitSearch();
private native void nativeFinishSearch();
private static native SearchAdapter.SearchResult nativeGetResult(int position, int queryID);
private native void nativeRunSearch(String s, String lang,
double lat, double lon, int mode, int queryID);
private static native void nativeShowItem(int position);
}