mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
sync from main
This commit is contained in:
@@ -112,12 +112,19 @@ limitations under the License.
|
||||
<meta-data android:name="com.google.android.backup.api_key"
|
||||
android:value="AEdPqrEAAAAIi-_QiwoRSc9_bAC9cmuNXTQyU8ajJmGtKdhskQ" />
|
||||
|
||||
<!-- Main track data provider -->
|
||||
<provider android:name="com.google.android.apps.mytracks.content.MyTracksProvider"
|
||||
android:authorities="com.google.android.maps.mytracks"
|
||||
android:exported="true"
|
||||
android:readPermission="com.google.android.apps.mytracks.READ_TRACK_DATA"
|
||||
android:writePermission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
|
||||
|
||||
<!-- Provider for search suggestions -->
|
||||
<provider android:name="com.google.android.apps.mytracks.content.SearchEngineProvider"
|
||||
android:authorities="com.google.android.maps.mytracks.search"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Main activity -->
|
||||
<activity android:name="com.google.android.apps.mytracks.MyTracks"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale">
|
||||
<intent-filter>
|
||||
@@ -129,7 +136,12 @@ limitations under the License.
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
|
||||
<data android:mimeType="vnd.android.cursor.item/vnd.google.waypoint" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Enable searches via SearchActivity. -->
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.MapActivity"
|
||||
@@ -137,13 +149,26 @@ limitations under the License.
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.TAB" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Enable searches via SearchActivity. -->
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
|
||||
<!-- TODO: VIEW and PICK for track cursor dir -->
|
||||
<activity android:name="com.google.android.apps.mytracks.TrackList" />
|
||||
|
||||
<!-- TODO: Merge TrackList, WaypointsLIst and SearchActivity, then implement VIEW and PICK for track and waypoint cursor dir -->
|
||||
<activity android:name="com.google.android.apps.mytracks.TrackList">
|
||||
<!-- Enable searches via SearchActivity. -->
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
|
||||
<!-- TODO: VIEW and PICK for waypoint cursor dir -->
|
||||
<activity android:name="com.google.android.apps.mytracks.WaypointsList" />
|
||||
<activity android:name="com.google.android.apps.mytracks.WaypointsList">
|
||||
<!-- Enable searches via SearchActivity. -->
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.TrackDetail"
|
||||
android:label="@string/track_detail_title">
|
||||
@@ -168,7 +193,6 @@ limitations under the License.
|
||||
<activity android:name="com.google.android.apps.mytracks.WaypointDetails"
|
||||
android:label="@string/marker_detail_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
@@ -191,6 +215,10 @@ limitations under the License.
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.TAB" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Enable searches via SearchActivity. -->
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.SensorStateActivity"
|
||||
@@ -201,6 +229,10 @@ limitations under the License.
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.TAB" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Enable searches via SearchActivity. -->
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.SendActivity"
|
||||
@@ -241,6 +273,15 @@ limitations under the License.
|
||||
android:label="@string/welcome_title"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.SearchActivity"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.searchable"
|
||||
android:resource="@xml/search"/>
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.io.docs.SendDocsActivity"/>
|
||||
<activity android:name="com.google.android.apps.mytracks.io.fusiontables.SendFusionTablesActivity"/>
|
||||
<activity android:name="com.google.android.apps.mytracks.io.maps.ChooseMapActivity"
|
||||
@@ -259,7 +300,7 @@ limitations under the License.
|
||||
|
||||
<service android:name="com.google.android.apps.mytracks.services.ControlRecordingService"
|
||||
android:exported="false" />
|
||||
|
||||
|
||||
<receiver android:name="com.google.android.apps.mytracks.BootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2011 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/menu_record_track"
|
||||
android:icon="@drawable/ic_menu_recordtrack"
|
||||
@@ -16,20 +31,24 @@
|
||||
android:icon="@drawable/ic_menu_waypoints"
|
||||
android:title="@string/menu_markers"/>
|
||||
|
||||
<item android:id="@+id/menu_search"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
android:title="@string/menu_search"/>
|
||||
|
||||
<item android:id="@+id/menu_sensor_state"
|
||||
android:icon="@drawable/ic_menu_sensorstatus"
|
||||
android:orderInCategory="1000"
|
||||
android:title="@string/menu_sensor_state"/>
|
||||
|
||||
|
||||
<item android:id="@+id/menu_settings"
|
||||
android:icon="@android:drawable/ic_menu_preferences"
|
||||
android:orderInCategory="1001"
|
||||
android:title="@string/menu_settings"/>
|
||||
|
||||
|
||||
<item android:id="@+id/menu_aggregated_statistics"
|
||||
android:orderInCategory="1002"
|
||||
android:title="@string/menu_aggregated_statistics"/>
|
||||
|
||||
|
||||
<item android:id="@+id/menu_help"
|
||||
android:icon="@android:drawable/ic_menu_info_details"
|
||||
android:orderInCategory="1003"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2012 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:id="@+id/menu_search"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
android:title="@string/menu_search"
|
||||
android:onClick="onSearch"
|
||||
android:showAsAction="always" />
|
||||
</menu>
|
||||
@@ -935,6 +935,10 @@ limitations under the License.
|
||||
Screenshot: menu_record_track.png">
|
||||
Tracks
|
||||
</string>
|
||||
<string name="menu_search"
|
||||
translation_description="In the menu, to start a textual search for tracks or waypoints.">
|
||||
Search
|
||||
</string>
|
||||
|
||||
<!-- My Location -->
|
||||
<string name="my_location_approximate_location"
|
||||
@@ -1092,6 +1096,24 @@ limitations under the License.
|
||||
Finished writing to the SD card.
|
||||
</string>
|
||||
|
||||
<!-- Search interface -->
|
||||
<string name="search_hint"
|
||||
translation_description="The hint shown on the search box until the user types a search query.
|
||||
See suggestion at http://developer.android.com/guide/topics/search/search-dialog.html about
|
||||
keeping this consistent with other Android apps.">
|
||||
Search My Tracks tracks and waypoints
|
||||
</string>
|
||||
<string name="search_global_description"
|
||||
translation_description="Description of what is searched in My Tracks, to be displayed in the
|
||||
global system search settings.">
|
||||
My Tracks tracks and waypoints
|
||||
</string>
|
||||
<string name="search_voice_prompt"
|
||||
translation_description="Prompt shown when the user activates voice search, asking him to
|
||||
speak what will be searched for.">
|
||||
Speak the name of a track or waypoint.
|
||||
</string>
|
||||
|
||||
<!-- Send Google -->
|
||||
<string name="send_google_by_my_tracks"
|
||||
translation_description="When sending a track to Google, a note indicating it is created by
|
||||
@@ -1716,13 +1738,6 @@ limitations under the License.
|
||||
Screenshot: settings_recording_top.png">
|
||||
Voice
|
||||
</string>
|
||||
<string name="settings_recording_voice_not_available"
|
||||
translation_description="In the 'Recording' settings, the summary for the 'Voice'
|
||||
explaining that this option is not available on your Android phone.
|
||||
|
||||
Screenshot: settings_recording_voice_not_available.png">
|
||||
Voice announcement is not available on your Android phone.
|
||||
</string>
|
||||
<string name="settings_recording_voice_summary"
|
||||
translation_description="In the 'Recording' settings, the summary for the 'Voice'
|
||||
dropdown option explaining that it configures the time or distance between two voice
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:hint="@string/search_hint"
|
||||
android:includeInGlobalSearch="true"
|
||||
android:label="@string/my_tracks_app_name"
|
||||
android:searchSettingsDescription="@string/search_global_description"
|
||||
android:voicePromptText="@string/search_voice_prompt"
|
||||
android:voiceSearchMode="showVoiceSearchButton|launchRecognizer"
|
||||
android:searchSuggestAuthority="com.google.android.maps.mytracks.search"
|
||||
android:searchSuggestThreshold="2"
|
||||
android:searchSuggestSelection=" ? " />
|
||||
@@ -26,10 +26,9 @@ import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
@@ -40,6 +39,7 @@ import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -116,9 +116,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.mytracks_charts);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
@@ -63,8 +63,7 @@ class CheckUnits {
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = useMetricPreferences.edit();
|
||||
String key = context.getString(R.string.metric_units_key);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
|
||||
editor.putBoolean(key, metric));
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor.putBoolean(key, metric));
|
||||
}
|
||||
});
|
||||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@@ -76,7 +75,7 @@ class CheckUnits {
|
||||
}
|
||||
|
||||
private static void recordCheckPerformed(SharedPreferences preferences) {
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(
|
||||
preferences.edit().putBoolean(CHECK_UNITS_PREFERENCE_KEY, true));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
@@ -66,7 +66,7 @@ public class DeleteAllTracks extends Handler {
|
||||
Editor editor = prefs.edit();
|
||||
// TODO: Go through data manager
|
||||
editor.putLong(context.getString(R.string.selected_track_key), -1);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
if (done != null) {
|
||||
Handler h = new Handler();
|
||||
h.post(done);
|
||||
|
||||
@@ -19,7 +19,7 @@ import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.apps.mytracks.util.UriUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -110,7 +110,7 @@ public class DeleteTrack extends Activity
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
if (preferences.getLong(selectedKey, -1) == deleteTrackId) {
|
||||
Editor editor = preferences.edit().putLong(selectedKey, -1);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,12 @@ public class ExportAllTracks {
|
||||
|
||||
String exportFileFormat = activity.getString(R.string.track_list_export_file);
|
||||
String fileTypes[] = activity.getResources().getStringArray(R.array.file_types);
|
||||
|
||||
|
||||
String[] choices = new String[fileTypes.length];
|
||||
for (int i = 0; i < fileTypes.length; i++) {
|
||||
choices[i] = String.format(exportFileFormat, fileTypes[i]);
|
||||
}
|
||||
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(R.string.track_list_export_all);
|
||||
builder.setSingleChoiceItems(choices, 0, itemClick);
|
||||
@@ -160,7 +160,7 @@ public class ExportAllTracks {
|
||||
try {
|
||||
MyTracksProviderUtils providerUtils =
|
||||
MyTracksProviderUtils.Factory.get(activity);
|
||||
cursor = providerUtils.getTracksCursor("");
|
||||
cursor = providerUtils.getTracksCursor(null, null, TracksColumns._ID);
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,8 @@ import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.file.SaveActivity;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.GeoRect;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
@@ -44,6 +43,7 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.Settings;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
@@ -86,6 +86,25 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
*/
|
||||
private boolean keepMyLocationVisible;
|
||||
|
||||
/**
|
||||
* The ID of a track on which we want to show a waypoint.
|
||||
* The waypoint will be shown as soon as the track is loaded.
|
||||
*/
|
||||
private long showWaypointTrackId;
|
||||
|
||||
/**
|
||||
* The ID of a waypoint which we want to show.
|
||||
* The waypoint will be shown as soon as its track is loaded.
|
||||
*/
|
||||
private long showWaypointId;
|
||||
|
||||
/**
|
||||
* The track that's currently selected.
|
||||
* This differs from {@link TrackDataHub#getSelectedTrackId} in that this one is only set after
|
||||
* actual track data has been received.
|
||||
*/
|
||||
private long selectedTrackId;
|
||||
|
||||
/**
|
||||
* The current pointer location.
|
||||
* This is kept to quickly center on it when the user requests.
|
||||
@@ -134,9 +153,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
super.onCreate(bundle);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
// We don't need a window title bar:
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
@@ -332,6 +349,47 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms and pans the map so that the given waypoint is visible, when the given track is loaded.
|
||||
* If the track is already loaded, it does that immediately.
|
||||
*
|
||||
* @param trackId the ID of the track on which to show the waypoint
|
||||
* @param waypointId the ID of the waypoint to show
|
||||
*/
|
||||
public void showWaypoint(long trackId, long waypointId) {
|
||||
synchronized (this) {
|
||||
if (trackId == selectedTrackId) {
|
||||
showWaypoint(waypointId);
|
||||
return;
|
||||
}
|
||||
|
||||
showWaypointTrackId = trackId;
|
||||
showWaypointId = waypointId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the proper zooming/panning for a just-loaded track.
|
||||
* This may be either zooming to a waypoint that has been previously selected, or
|
||||
* zooming to the whole track.
|
||||
*
|
||||
* @param track the loaded track
|
||||
*/
|
||||
private void zoomLoadedTrack(Track track) {
|
||||
synchronized (this) {
|
||||
if (track.getId() == showWaypointTrackId) {
|
||||
// There's a waypoint to show in this track.
|
||||
showWaypoint(showWaypointId);
|
||||
|
||||
showWaypointId = 0L;
|
||||
showWaypointTrackId = 0L;
|
||||
} else {
|
||||
// Zoom out to show the whole track.
|
||||
zoomMapToBoundaries(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -345,7 +403,12 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
if (trackSelected) {
|
||||
busyPane.setVisibility(View.VISIBLE);
|
||||
|
||||
zoomMapToBoundaries(track);
|
||||
synchronized (this) {
|
||||
// Need to get the track ID only at this point, to prevent a race condition
|
||||
// among showWaypoint, zoomLoadedTrack and dataHub.loadTrack.
|
||||
selectedTrackId = track.getId();
|
||||
zoomLoadedTrack(track);
|
||||
}
|
||||
|
||||
mapOverlay.setShowEndMarker(!isRecording);
|
||||
busyPane.setVisibility(View.GONE);
|
||||
|
||||
@@ -37,6 +37,8 @@ class MenuManager {
|
||||
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
activity.getMenuInflater().inflate(R.menu.main, menu);
|
||||
|
||||
// TODO: Replace search button with search widget if API level >= 11
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -85,6 +87,10 @@ class MenuManager {
|
||||
case R.id.menu_help: {
|
||||
return startActivity(WelcomeActivity.class);
|
||||
}
|
||||
case R.id.menu_search: {
|
||||
// TODO: Pass the current track ID and current location to do some fancier ranking.
|
||||
activity.onSearchRequested();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -21,13 +21,15 @@ import com.google.android.apps.analytics.GoogleAnalyticsTracker;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.apps.mytracks.io.file.TempFileCleaner;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.apps.mytracks.util.EulaUtil;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.apps.mytracks.util.UriUtils;
|
||||
@@ -46,6 +48,7 @@ import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -67,7 +70,7 @@ import android.widget.Toast;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
private static final int DIALOG_EULA_ID = 0;
|
||||
|
||||
|
||||
private TrackDataHub dataHub;
|
||||
|
||||
/**
|
||||
@@ -139,9 +142,8 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "MyTracks.onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
ApiFeatures apiFeatures = ApiFeatures.getInstance();
|
||||
if (!SystemUtils.isRelease(this)) {
|
||||
apiFeatures.getApiAdapter().enableStrictMode();
|
||||
ApiAdapterFactory.getApiAdapter().enableStrictMode();
|
||||
}
|
||||
|
||||
tracker = GoogleAnalyticsTracker.getInstance();
|
||||
@@ -157,14 +159,14 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
menuManager = new MenuManager(this);
|
||||
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindCallback);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(apiFeatures).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
// We don't need a window title bar:
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
// If the user just starts typing (on a device with a keyboard), we start a search.
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
final Resources res = getResources();
|
||||
final TabHost tabHost = getTabHost();
|
||||
tabHost.addTab(tabHost.newTabSpec("tab1")
|
||||
@@ -199,7 +201,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
showDialog(DIALOG_EULA_ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Log.d(TAG, "MyTracks.onStart");
|
||||
@@ -219,6 +221,19 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
&& UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
|
||||
long trackId = ContentUris.parseId(data);
|
||||
dataHub.loadTrack(trackId);
|
||||
} else if (Intent.ACTION_VIEW.equals(action)
|
||||
&& WaypointsColumns.CONTENT_ITEMTYPE.equals(intent.getType())
|
||||
&& UriUtils.matchesContentUri(data, WaypointsColumns.CONTENT_URI)) {
|
||||
// TODO(rdamazio): Waypoint URIs should be base/trackid/waypointid
|
||||
long waypointId = ContentUris.parseId(data);
|
||||
Waypoint waypoint = providerUtils.getWaypoint(waypointId);
|
||||
long trackId = waypoint.getTrackId();
|
||||
|
||||
// Request that the waypoint is shown (now or when the right track is loaded).
|
||||
showWaypoint(trackId, waypointId);
|
||||
|
||||
// Load the right track, if not loaded already.
|
||||
dataHub.loadTrack(trackId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +308,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
@@ -366,12 +381,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
if (results != null) {
|
||||
final long waypointId = results.getLongExtra(WaypointDetails.WAYPOINT_ID_EXTRA, -1);
|
||||
if (waypointId >= 0) {
|
||||
MapActivity map =
|
||||
(MapActivity) getLocalActivityManager().getActivity("tab1");
|
||||
if (map != null) {
|
||||
getTabHost().setCurrentTab(0);
|
||||
map.showWaypoint(waypointId);
|
||||
}
|
||||
showWaypoint(trackId, waypointId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -387,6 +397,17 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void showWaypoint(long trackId, long waypointId) {
|
||||
MapActivity map =
|
||||
(MapActivity) getLocalActivityManager().getActivity("tab1");
|
||||
if (map != null) {
|
||||
getTabHost().setCurrentTab(0);
|
||||
map.showWaypoint(trackId, waypointId);
|
||||
} else {
|
||||
Log.e(TAG, "Couldnt' get map tab");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.SearchEngine;
|
||||
import com.google.android.apps.mytracks.content.SearchEngine.ScoredResult;
|
||||
import com.google.android.apps.mytracks.content.SearchEngine.SearchQuery;
|
||||
import com.google.android.apps.mytracks.content.SearchEngineProvider;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* Activity to search for tracks or waypoints.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SearchActivity extends ListActivity {
|
||||
|
||||
private static final String EXTRA_CURRENT_TRACK_ID = "trackId";
|
||||
|
||||
private static final boolean LOG_SCORES = true;
|
||||
|
||||
private SearchEngine engine;
|
||||
private LocationManager locationManager;
|
||||
|
||||
private SearchRecentSuggestions suggestions;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
engine = new SearchEngine(this);
|
||||
suggestions = SearchEngineProvider.newHelper(this);
|
||||
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
setIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
if (!Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
Log.e(TAG, "Got bad search intent: " + intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
String textQuery = intent.getStringExtra(SearchManager.QUERY);
|
||||
Location currentLocation = locationManager.getLastKnownLocation("gps");
|
||||
long currentTrackId = intent.getLongExtra(EXTRA_CURRENT_TRACK_ID, -1);
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
|
||||
final SearchQuery query =
|
||||
new SearchQuery(textQuery, currentLocation, currentTrackId, currentTimestamp);
|
||||
|
||||
// Do the actual search in a separate thread.
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
doSearch(query);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void doSearch(SearchQuery query) {
|
||||
SortedSet<ScoredResult> scoredResults = engine.search(query);
|
||||
|
||||
final List<? extends Map<String, ?>> displayResults = prepareResultsforDisplay(scoredResults);
|
||||
|
||||
if (LOG_SCORES) {
|
||||
Log.i(TAG, "Search scores: " + displayResults);
|
||||
}
|
||||
|
||||
// Then go back to the UI thread to display them.
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showSearchResults(displayResults);
|
||||
}
|
||||
});
|
||||
|
||||
// Save the query as a suggestion for the future.
|
||||
suggestions.saveRecentQuery(query.textQuery, null);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> prepareResultsforDisplay(
|
||||
Collection<ScoredResult> scoredResults) {
|
||||
ArrayList<Map<String, Object>> output = new ArrayList<Map<String, Object>>(scoredResults.size());
|
||||
for (ScoredResult result : scoredResults) {
|
||||
Map<String, Object> resultMap = new HashMap<String, Object>();
|
||||
if (result.track != null) {
|
||||
prepareTrackForDisplay(result.track, resultMap);
|
||||
} else {
|
||||
prepareWaypointForDisplay(result.waypoint, resultMap);
|
||||
}
|
||||
|
||||
resultMap.put("score", result.score);
|
||||
|
||||
output.add(resultMap);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private void prepareWaypointForDisplay(Waypoint waypoint, Map<String, Object> resultMap) {
|
||||
// TODO: Yellow pushpin for statistics marker.
|
||||
resultMap.put("icon", R.drawable.blue_pushpin);
|
||||
resultMap.put("name", waypoint.getName());
|
||||
resultMap.put("description", waypoint.getDescription());
|
||||
resultMap.put("category", waypoint.getCategory());
|
||||
resultMap.put("time", String.format("%tc", waypoint.getLocation().getTime()));
|
||||
resultMap.put("trackId", waypoint.getTrackId());
|
||||
resultMap.put("waypointId", waypoint.getId());
|
||||
}
|
||||
|
||||
private void prepareTrackForDisplay(Track track, Map<String, Object> resultMap) {
|
||||
resultMap.put("icon", R.drawable.track);
|
||||
resultMap.put("name", track.getName());
|
||||
resultMap.put("description", track.getDescription());
|
||||
resultMap.put("category", track.getCategory());
|
||||
resultMap.put("time", String.format("%tc", track.getStatistics().getStartTime()));
|
||||
resultMap.put("trackId", track.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the given search results.
|
||||
* Must be run from the UI thread.
|
||||
* @param data
|
||||
*
|
||||
* @param scoredResults the results to show, properly ordered
|
||||
*/
|
||||
private void showSearchResults(List<? extends Map<String, ?>> data) {
|
||||
SimpleAdapter adapter = new SimpleAdapter(this, data,
|
||||
// TODO: Custom view for search results.
|
||||
R.layout.mytracks_list_item,
|
||||
new String[] {
|
||||
"icon",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"time"
|
||||
},
|
||||
new int[] {
|
||||
R.id.trackdetails_item_icon,
|
||||
R.id.trackdetails_item_name,
|
||||
R.id.trackdetails_item_description,
|
||||
R.id.trackdetails_item_category,
|
||||
R.id.trackdetails_item_time
|
||||
});
|
||||
|
||||
setListAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> clickedData = (Map<String, Object>) getListAdapter().getItem(position);
|
||||
|
||||
startActivity(createViewDataIntent(clickedData));
|
||||
}
|
||||
|
||||
private Intent createViewDataIntent(Map<String, Object> clickedData) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
if (clickedData.containsKey("waypointId")) {
|
||||
long waypointId = (Long) clickedData.get("waypointId");
|
||||
Uri uri = ContentUris.withAppendedId(WaypointsColumns.CONTENT_URI, waypointId);
|
||||
intent.setDataAndType(uri, WaypointsColumns.CONTENT_ITEMTYPE);
|
||||
} else {
|
||||
long trackId = (Long) clickedData.get("trackId");
|
||||
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
|
||||
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,13 @@ import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
|
||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntUtils;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
@@ -44,6 +44,7 @@ import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.provider.Settings;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -102,10 +103,7 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
super.onCreate(icicle);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
ApiFeatures apiFeatures = ApiFeatures.getInstance();
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(apiFeatures).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
// Tell it where to read/write preferences
|
||||
PreferenceManager preferenceManager = getPreferenceManager();
|
||||
@@ -113,23 +111,13 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
preferenceManager.setSharedPreferencesMode(0);
|
||||
|
||||
// Set up automatic preferences backup
|
||||
backupListener = apiFeatures.getApiAdapter().getBackupPreferencesListener(this);
|
||||
backupListener = ApiAdapterFactory.getApiAdapter().getBackupPreferencesListener(this);
|
||||
preferences = preferenceManager.getSharedPreferences();
|
||||
preferences.registerOnSharedPreferenceChangeListener(backupListener);
|
||||
|
||||
// Load the preferences to be displayed
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
// Disable voice announcement if not available
|
||||
if (!apiFeatures.hasTextToSpeech()) {
|
||||
IntegerListPreference announcementFrequency =
|
||||
(IntegerListPreference) findPreference(
|
||||
getString(R.string.announcement_frequency_key));
|
||||
announcementFrequency.setEnabled(false);
|
||||
announcementFrequency.setValue(TASK_FREQUENCY_OFF);
|
||||
announcementFrequency.setSummary(R.string.settings_recording_voice_not_available);
|
||||
}
|
||||
|
||||
setRecordingIntervalOptions();
|
||||
setAutoResumeTimeoutOptions();
|
||||
|
||||
@@ -557,20 +545,17 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
* Configures preference actions related to bluetooth.
|
||||
*/
|
||||
private void configureBluetoothPreferences() {
|
||||
if (BluetoothDeviceUtils.isBluetoothMethodSupported()) {
|
||||
// Populate the list of bluetooth devices
|
||||
populateBluetoothDeviceList();
|
||||
|
||||
// Make the pair devices preference go to the system preferences
|
||||
findPreference(getString(R.string.bluetooth_pairing_key))
|
||||
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent settingsIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
|
||||
startActivity(settingsIntent);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
// Populate the list of bluetooth devices
|
||||
populateBluetoothDeviceList();
|
||||
// Make the pair devices preference go to the system preferences
|
||||
findPreference(getString(R.string.bluetooth_pairing_key)).setOnPreferenceClickListener(
|
||||
new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent settingsIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
|
||||
startActivity(settingsIntent);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -582,7 +567,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
List<String> entryValues = new ArrayList<String>();
|
||||
|
||||
// The actual devices
|
||||
BluetoothDeviceUtils.getInstance().populateDeviceLists(entries, entryValues);
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bluetoothAdapter != null) {
|
||||
BluetoothDeviceUtils.populateDeviceLists(bluetoothAdapter, entries, entryValues);
|
||||
}
|
||||
|
||||
CharSequence[] entriesArray = entries.toArray(new CharSequence[entries.size()]);
|
||||
CharSequence[] entryValuesArray = entryValues.toArray(new CharSequence[entryValues.size()]);
|
||||
@@ -685,7 +673,7 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
|
||||
Editor editor = prefs.edit();
|
||||
editor.putString(getString(id), metricspeed);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,7 @@ import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -32,6 +31,7 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Window;
|
||||
@@ -105,9 +105,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
utils = new StatsUtilities(this);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
// We don't need a window title bar:
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
@@ -286,6 +286,17 @@ public class TrackList extends ListActivity
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.search_only, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Callback from menu/search_only.xml */
|
||||
public void onSearch(@SuppressWarnings("unused") MenuItem i) {
|
||||
onSearchRequested();
|
||||
}
|
||||
|
||||
private void updateButtonsEnabled() {
|
||||
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
|
||||
View exportAll = findViewById(R.id.tracklist_btn_export_all);
|
||||
|
||||
@@ -198,6 +198,17 @@ public class WaypointsList extends ListActivity
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.search_only, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Callback from menu/search_only.xml */
|
||||
public void onSearch(@SuppressWarnings("unused") MenuItem i) {
|
||||
onSearchRequested();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
WaypointCreationRequest request;
|
||||
@@ -266,10 +277,10 @@ public class WaypointsList extends ListActivity
|
||||
if (columnIndex == timeIdx) {
|
||||
long time = cursor.getLong(timeIdx);
|
||||
TextView textView = (TextView) view;
|
||||
|
||||
|
||||
if (time == 0) {
|
||||
textView.setVisibility(View.GONE);
|
||||
} else {
|
||||
} else {
|
||||
textView.setText(StringUtils.formatDateTime(WaypointsList.this, time));
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
@@ -417,12 +416,9 @@ public class MyTracksProvider extends ContentProvider {
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown URL " + url);
|
||||
}
|
||||
|
||||
if (ApiFeatures.getInstance().canReuseSQLiteQueryBuilder()) {
|
||||
Log.i(Constants.TAG,
|
||||
"Build query: " + qb.buildQuery(projection, selection, selectionArgs,
|
||||
null, null, sortOrder, null));
|
||||
}
|
||||
Log.i(Constants.TAG, "Build query: "
|
||||
+ qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null));
|
||||
|
||||
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null,
|
||||
sortOrder);
|
||||
c.setNotificationUri(getContext().getContentResolver(), url);
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Engine for searching for tracks and waypoints by text.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SearchEngine {
|
||||
|
||||
/** WHERE query to get tracks by name. */
|
||||
private static final String TRACK_SELECTION_QUERY =
|
||||
TracksColumns.NAME + " LIKE ? OR " +
|
||||
TracksColumns.DESCRIPTION + " LIKE ? OR " +
|
||||
TracksColumns.CATEGORY + " LIKE ?";
|
||||
|
||||
/** WHERE query to get waypoints by name. */
|
||||
private static final String WAYPOINT_SELECTION_QUERY =
|
||||
WaypointsColumns.NAME + " LIKE ? OR " +
|
||||
WaypointsColumns.DESCRIPTION + " LIKE ? OR " +
|
||||
WaypointsColumns.CATEGORY + " LIKE ?";
|
||||
|
||||
/** Order of track results. */
|
||||
private static final String TRACK_SELECTION_ORDER = TracksColumns._ID + " DESC LIMIT 1000";
|
||||
|
||||
/** Order of waypoint results. */
|
||||
private static final String WAYPOINT_SELECTION_ORDER = WaypointsColumns._ID + " DESC";
|
||||
|
||||
/** How much we promote a match in the track category. */
|
||||
private static final double TRACK_CATEGORY_PROMOTION = 2.0;
|
||||
|
||||
/** How much we promote a match in the track description. */
|
||||
private static final double TRACK_DESCRIPTION_PROMOTION = 8.0;
|
||||
|
||||
/** How much we promote a match in the track name. */
|
||||
private static final double TRACK_NAME_PROMOTION = 16.0;
|
||||
|
||||
/** How much we promote a waypoint result if it's in the currently-selected track. */
|
||||
private static final double CURRENT_TRACK_WAYPOINT_PROMOTION = 2.0;
|
||||
|
||||
/** How much we promote a track result if it's the currently-selected track. */
|
||||
private static final double CURRENT_TRACK_DEMOTION = 0.5;
|
||||
|
||||
/** Maximum number of waypoints which will be retrieved and scored. */
|
||||
private static final int MAX_SCORED_WAYPOINTS = 100;
|
||||
|
||||
/** Oldest timestamp for which we rank based on time (2000-01-01 00:00:00.000) */
|
||||
private static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L;
|
||||
|
||||
/**
|
||||
* Description of a search query, along with all contextual data needed to execute it.
|
||||
*/
|
||||
public static class SearchQuery {
|
||||
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId,
|
||||
long currentTimestamp) {
|
||||
this.textQuery = textQuery.toLowerCase();
|
||||
this.currentLocation = currentLocation;
|
||||
this.currentTrackId = currentTrackId;
|
||||
this.currentTimestamp = currentTimestamp;
|
||||
}
|
||||
|
||||
public final String textQuery;
|
||||
public final Location currentLocation;
|
||||
public final long currentTrackId;
|
||||
public final long currentTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of a search result which has been retrieved and scored.
|
||||
*/
|
||||
public static class ScoredResult {
|
||||
ScoredResult(Track track, double score) {
|
||||
this.track = track;
|
||||
this.waypoint = null;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
ScoredResult(Waypoint waypoint, double score) {
|
||||
this.track = null;
|
||||
this.waypoint = waypoint;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
public final Track track;
|
||||
public final Waypoint waypoint;
|
||||
public final double score;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoredResult ["
|
||||
+ (track != null ? ("trackId=" + track.getId() + ", ") : "")
|
||||
+ (waypoint != null ? ("wptId=" + waypoint.getId() + ", ") : "")
|
||||
+ "score=" + score + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/** Comparador for scored results. */
|
||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR =
|
||||
new Comparator<ScoredResult>() {
|
||||
@Override
|
||||
public int compare(ScoredResult r1, ScoredResult r2) {
|
||||
// Score ordering.
|
||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||
if (scoreDiff != 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
// Make tracks come before waypoints.
|
||||
if (r1.waypoint != null && r2.track != null) {
|
||||
return 1;
|
||||
} else if (r1.track != null && r2.waypoint != null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Finally, use arbitrary ordering, by ID.
|
||||
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
|
||||
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
|
||||
long idDiff = id2 - id1;
|
||||
return Long.signum(idDiff);
|
||||
}
|
||||
};
|
||||
|
||||
private final MyTracksProviderUtils providerUtils;
|
||||
|
||||
public SearchEngine(Context ctx) {
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(ctx);
|
||||
}
|
||||
|
||||
public SearchEngine(MyTracksProviderUtils providerUtils) {
|
||||
this.providerUtils = providerUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a search query and returns a set of sorted results.
|
||||
*
|
||||
* @param query the query to execute
|
||||
* @return a set of results, sorted according to their score
|
||||
*/
|
||||
public SortedSet<ScoredResult> search(SearchQuery query) {
|
||||
ArrayList<Track> tracks = new ArrayList<Track>();
|
||||
ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>();
|
||||
TreeSet<ScoredResult> scoredResults = new TreeSet<ScoredResult>(SCORED_RESULT_COMPARATOR);
|
||||
|
||||
retrieveTracks(query, tracks);
|
||||
retrieveWaypoints(query, waypoints);
|
||||
|
||||
scoreTrackResults(tracks, query, scoredResults);
|
||||
scoreWaypointResults(waypoints, query, scoredResults);
|
||||
|
||||
return scoredResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves tracks matching the given query from the database.
|
||||
*
|
||||
* @param query the query to retrieve for
|
||||
* @param tracks list to fill with the resulting tracks
|
||||
*/
|
||||
private void retrieveTracks(SearchQuery query, ArrayList<Track> tracks) {
|
||||
String queryLikeSelection = "%" + query.textQuery + "%";
|
||||
String[] trackSelectionArgs = new String[] {
|
||||
queryLikeSelection,
|
||||
queryLikeSelection,
|
||||
queryLikeSelection };
|
||||
|
||||
Cursor tracksCursor = providerUtils.getTracksCursor(
|
||||
TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER);
|
||||
if (tracksCursor != null) {
|
||||
try {
|
||||
tracks.ensureCapacity(tracksCursor.getCount());
|
||||
|
||||
while (tracksCursor.moveToNext()) {
|
||||
tracks.add(providerUtils.createTrack(tracksCursor));
|
||||
}
|
||||
} finally {
|
||||
tracksCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves waypoints matching the given query from the database.
|
||||
*
|
||||
* @param query the query to retrieve for
|
||||
* @param tracks list to fill with the resulting waypoints
|
||||
*/
|
||||
private void retrieveWaypoints(SearchQuery query, ArrayList<Waypoint> waypoints) {
|
||||
String queryLikeSelection2 = "%" + query.textQuery + "%";
|
||||
String[] waypointSelectionArgs = new String[] {
|
||||
queryLikeSelection2,
|
||||
queryLikeSelection2,
|
||||
queryLikeSelection2 };
|
||||
Cursor waypointsCursor = providerUtils.getWaypointsCursor(
|
||||
WAYPOINT_SELECTION_QUERY, waypointSelectionArgs, WAYPOINT_SELECTION_ORDER,
|
||||
MAX_SCORED_WAYPOINTS);
|
||||
if (waypointsCursor != null) {
|
||||
try {
|
||||
waypoints.ensureCapacity(waypointsCursor.getCount());
|
||||
|
||||
while (waypointsCursor.moveToNext()) {
|
||||
waypoints.add(providerUtils.createWaypoint(waypointsCursor));
|
||||
}
|
||||
} finally {
|
||||
waypointsCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scores a collection of track results.
|
||||
*
|
||||
* @param tracks the results to score
|
||||
* @param query the query to score for
|
||||
* @param output the collection to fill with scored results
|
||||
*/
|
||||
private void scoreTrackResults(Collection<Track> tracks, SearchQuery query, Collection<ScoredResult> output) {
|
||||
for (Track track : tracks) {
|
||||
// Calculate the score.
|
||||
double score = scoreTrackResult(query, track);
|
||||
|
||||
// Add to the output.
|
||||
output.add(new ScoredResult(track, score));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scores a single track result.
|
||||
*
|
||||
* @param query the query to score for
|
||||
* @param track the results to score
|
||||
* @return the score for the track
|
||||
*/
|
||||
private double scoreTrackResult(SearchQuery query, Track track) {
|
||||
double score = 1.0;
|
||||
|
||||
score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory());
|
||||
|
||||
TripStatistics statistics = track.getStatistics();
|
||||
// TODO: Also boost for proximity to the currently-centered position on the map.
|
||||
score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude());
|
||||
|
||||
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
|
||||
score *= getTimeBoost(query, meanTimestamp);
|
||||
|
||||
// Score the currently-selected track lower (user is already there, wouldn't be searching for it).
|
||||
if (track.getId() == query.currentTrackId) {
|
||||
score *= CURRENT_TRACK_DEMOTION;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scores a collection of waypoint results.
|
||||
*
|
||||
* @param waypoints the results to score
|
||||
* @param query the query to score for
|
||||
* @param output the collection to fill with scored results
|
||||
*/
|
||||
private void scoreWaypointResults(Collection<Waypoint> waypoints, SearchQuery query, Collection<ScoredResult> output) {
|
||||
for (Waypoint waypoint : waypoints) {
|
||||
// Calculate the score.
|
||||
double score = scoreWaypointResult(query, waypoint);
|
||||
|
||||
// Add to the output.
|
||||
output.add(new ScoredResult(waypoint, score));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scores a single waypoint result.
|
||||
*
|
||||
* @param query the query to score for
|
||||
* @param waypoint the results to score
|
||||
* @return the score for the waypoint
|
||||
*/
|
||||
private double scoreWaypointResult(SearchQuery query, Waypoint waypoint) {
|
||||
double score = 1.0;
|
||||
|
||||
Location location = waypoint.getLocation();
|
||||
score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory());
|
||||
// TODO: Also boost for proximity to the currently-centered position on the map.
|
||||
score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude());
|
||||
score *= getTimeBoost(query, location.getTime());
|
||||
|
||||
// Score waypoints in the currently-selected track higher (searching inside the current track).
|
||||
if (query.currentTrackId != -1 && waypoint.getTrackId() == query.currentTrackId) {
|
||||
score *= CURRENT_TRACK_WAYPOINT_PROMOTION;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the boosting of the score due to the field(s) in which the match occured.
|
||||
*
|
||||
* @param query the query to boost for
|
||||
* @param name the name of the track or waypoint
|
||||
* @param description the description of the track or waypoint
|
||||
* @param category the category of the track or waypoint
|
||||
* @return the total boost to be applied to the result
|
||||
*/
|
||||
private double getTitleBoost(SearchQuery query,
|
||||
String name, String description, String category) {
|
||||
// Title boost: track name > description > category.
|
||||
double boost = 1.0;
|
||||
if (name.toLowerCase().contains(query.textQuery)) {
|
||||
boost *= TRACK_NAME_PROMOTION;
|
||||
}
|
||||
if (description.toLowerCase().contains(query.textQuery)) {
|
||||
boost *= TRACK_DESCRIPTION_PROMOTION;
|
||||
}
|
||||
if (category.toLowerCase().contains(query.textQuery)) {
|
||||
boost *= TRACK_CATEGORY_PROMOTION;
|
||||
}
|
||||
return boost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the boosting of the score due to the recency of the matched entity.
|
||||
*
|
||||
* @param query the query to boost for
|
||||
* @param timestamp the timestamp to calculate the boost for
|
||||
* @return the total boost to be applied to the result
|
||||
*/
|
||||
private double getTimeBoost(SearchQuery query, long timestamp) {
|
||||
if (timestamp < OLDEST_ALLOWED_TIMESTAMP) {
|
||||
// Safety: if timestamp is too old or invalid, don't rank based on time.
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Score recent tracks higher.
|
||||
long timeAgoHours = (query.currentTimestamp - timestamp) / (60L * 60L * 1000L);
|
||||
if (timeAgoHours > 0L) {
|
||||
return squash(timeAgoHours);
|
||||
} else {
|
||||
// Should rarely happen (track recorded in the last hour).
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the boosting of the score due to proximity to a location.
|
||||
*
|
||||
* @param query the query to boost for
|
||||
* @param latitude the latitude to calculate the boost for
|
||||
* @param longitude the longitude to calculate the boost for
|
||||
* @return the total boost to be applied to the result
|
||||
*/
|
||||
private double getDistanceBoost(SearchQuery query, double latitude, double longitude) {
|
||||
if (query.currentLocation == null) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float[] distanceResults = new float[1];
|
||||
|
||||
Location.distanceBetween(
|
||||
latitude, longitude,
|
||||
query.currentLocation.getLatitude(), query.currentLocation.getLongitude(),
|
||||
distanceResults);
|
||||
|
||||
// Score tracks close to the current location higher.
|
||||
double distanceKm = distanceResults[0] / 1000.0;
|
||||
if (distanceKm > 0.0) {
|
||||
// Use the inverse of the amortized distance.
|
||||
return squash(distanceKm);
|
||||
} else {
|
||||
// Should rarely happen (distance is exactly 0).
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Squashes a number by calculating 1 / log (1 + x).
|
||||
*/
|
||||
private static double squash(double x) {
|
||||
return 1.0 / Math.log1p(x);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SearchRecentSuggestionsProvider;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
|
||||
/**
|
||||
* Content provider for search suggestions.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SearchEngineProvider extends SearchRecentSuggestionsProvider {
|
||||
|
||||
private static final String AUTHORITY = "com.google.android.maps.mytracks.search";
|
||||
private static final int MODE = DATABASE_MODE_QUERIES;
|
||||
|
||||
public SearchEngineProvider() {
|
||||
setupSuggestions(AUTHORITY, MODE);
|
||||
}
|
||||
|
||||
// TODO: Also add suggestions from the database.
|
||||
|
||||
/**
|
||||
* Creates and returns a helper for adding recent queries or clearing the recent query history.
|
||||
*/
|
||||
public static SearchRecentSuggestions newHelper(Context context) {
|
||||
return new SearchRecentSuggestions(context, AUTHORITY, MODE);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBuff
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListeners.ListenerRegistration;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -196,7 +196,7 @@ public class TrackDataHub {
|
||||
private DoubleBufferedLocationFactory locationFactory;
|
||||
|
||||
private boolean started = false;
|
||||
|
||||
|
||||
/**
|
||||
* Builds a new {@link TrackDataHub} instance.
|
||||
*/
|
||||
@@ -274,7 +274,7 @@ public class TrackDataHub {
|
||||
Log.w(TAG, "Not started, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Unregister internal listeners even if there are external listeners registered.
|
||||
dataSourceManager.unregisterAllListeners();
|
||||
listenerHandlerThread.getLooper().quit();
|
||||
@@ -399,7 +399,7 @@ public class TrackDataHub {
|
||||
|
||||
// Save the selection to memory and flush.
|
||||
selectedTrackId = trackId;
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(
|
||||
preferences.edit().putLong(SELECTED_TRACK_KEY, trackId));
|
||||
|
||||
// Force it to reload data from the beginning.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import com.google.android.apps.mytracks.content.ContentTypeIds;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
@@ -106,7 +106,7 @@ class PreferenceBackupHelper {
|
||||
byte typeId = reader.readByte();
|
||||
readAndSetPreference(name, typeId, reader, editor);
|
||||
}
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
@@ -109,7 +109,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
||||
|
||||
context = activity.getApplicationContext();
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport();
|
||||
HttpTransport transport = ApiAdapterFactory.getApiAdapter().getHttpTransport();
|
||||
httpRequestFactory = transport.createRequestFactory(new MethodOverride());
|
||||
|
||||
canRetry = true;
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.accounts.Account;
|
||||
@@ -153,7 +153,7 @@ public class AccountChooserActivity extends Activity {
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
Editor editor = prefs.edit();
|
||||
editor.putString(getString(R.string.preferred_account_key), account.name);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ACCOUNT, account);
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -218,7 +218,7 @@ public class UploadServiceChooserActivity extends Activity {
|
||||
editor.putBoolean(getString(R.string.send_to_fusion_tables_key), sendFusionTables());
|
||||
editor.putBoolean(getString(R.string.send_to_docs_key), sendDocs());
|
||||
}
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -174,19 +174,19 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
||||
public void setAutoResumeTrackCurrentRetry(int retryAttempts) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putInt(autoResumeTrackCurrentRetryKey, retryAttempts);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
public void setRecordingTrack(long id) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putLong(recordingTrackKey, id);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
public void setSelectedTrack(long id) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putLong(selectedTrackKey, id);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
||||
@@ -36,14 +36,12 @@ import com.google.android.apps.mytracks.services.tasks.SplitTask;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiLevelAdapter;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ContentValues;
|
||||
@@ -80,7 +78,6 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private LocationManager locationManager;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
@@ -243,8 +240,6 @@ public class TrackRecordingService extends Service {
|
||||
super.onCreate();
|
||||
Log.d(TAG, "TrackRecordingService.onCreate");
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
notificationManager =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||
|
||||
setUpTaskExecutors();
|
||||
@@ -369,7 +364,6 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
// Make sure we have no indirect references to this service.
|
||||
locationManager = null;
|
||||
notificationManager = null;
|
||||
providerUtils = null;
|
||||
binder.detachFromService();
|
||||
binder = null;
|
||||
@@ -485,8 +479,6 @@ public class TrackRecordingService extends Service {
|
||||
* Shows the notification message and icon in the notification bar.
|
||||
*/
|
||||
private void showNotification() {
|
||||
final ApiLevelAdapter apiLevelAdapter =
|
||||
ApiFeatures.getInstance().getApiAdapter();
|
||||
if (isRecording) {
|
||||
Notification notification = new Notification(
|
||||
R.drawable.arrow_320, null /* tickerText */,
|
||||
@@ -497,16 +489,24 @@ public class TrackRecordingService extends Service {
|
||||
notification.setLatestEventInfo(this, getString(R.string.my_tracks_app_name),
|
||||
getString(R.string.track_record_notification), contentIntent);
|
||||
notification.flags += Notification.FLAG_NO_CLEAR;
|
||||
apiLevelAdapter.startForeground(this, notificationManager, 1,
|
||||
notification);
|
||||
startForegroundService(notification);
|
||||
} else {
|
||||
apiLevelAdapter.stopForeground(this, notificationManager, 1);
|
||||
stopForegroundService();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void startForegroundService(Notification notification) {
|
||||
startForeground(1, notification);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void stopForegroundService() {
|
||||
stopForeground(true);
|
||||
}
|
||||
|
||||
private void setUpTaskExecutors() {
|
||||
announcementExecutor = new PeriodicTaskExecutor(
|
||||
this, new StatusAnnouncerFactory(ApiFeatures.getInstance()));
|
||||
announcementExecutor = new PeriodicTaskExecutor(this, new StatusAnnouncerFactory());
|
||||
splitExecutor = new PeriodicTaskExecutor(this, new SplitTask.Factory());
|
||||
}
|
||||
|
||||
|
||||
+5
-34
@@ -18,7 +18,7 @@ package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
@@ -31,8 +31,6 @@ import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -44,8 +42,9 @@ import java.util.UUID;
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothConnectionManager {
|
||||
// Unique UUID for this application
|
||||
private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
// Unique Bluetooth UUID for My Tracks
|
||||
public static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
private MessageParser parser;
|
||||
|
||||
@@ -254,41 +253,13 @@ public class BluetoothConnectionManager {
|
||||
// Get a BluetoothSocket for a connection with the
|
||||
// given BluetoothDevice
|
||||
try {
|
||||
tmp = getSocket();
|
||||
tmp = ApiAdapterFactory.getApiAdapter().getBluetoothSocket(device);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "create() failed", e);
|
||||
}
|
||||
socket = tmp;
|
||||
}
|
||||
|
||||
private BluetoothSocket getSocket() throws IOException {
|
||||
if (ApiFeatures.getInstance().hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord()) {
|
||||
try {
|
||||
return device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Class<? extends BluetoothDevice> c = device.getClass();
|
||||
Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class);
|
||||
insecure.setAccessible(true);
|
||||
return (BluetoothSocket) insecure.invoke(device, 1);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
|
||||
}
|
||||
}
|
||||
return device.createRfcommSocketToServiceRecord(SPP_UUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(Constants.TAG, "BEGIN mConnectThread");
|
||||
|
||||
+2
-3
@@ -16,7 +16,7 @@
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -62,8 +62,7 @@ public class ZephyrMessageParser implements MessageParser {
|
||||
// 0x1A00316550003162 produces erroneous values for Cadence and needs
|
||||
// a workaround based on the stride counter.
|
||||
// Firmware values range from field 3 to 10 (inclusive) of the byte buffer.
|
||||
byte[] hardwareFirmwareId = ApiFeatures.getInstance().getApiAdapter()
|
||||
.copyByteArray(buffer, 3, 11);
|
||||
byte[] hardwareFirmwareId = ApiAdapterFactory.getApiAdapter().copyByteArray(buffer, 3, 11);
|
||||
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder();
|
||||
|
||||
if (Arrays.equals(hardwareFirmwareId, CADENCE_BUG_FW_ID)) {
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ import com.dsi.ant.AntMesg;
|
||||
import com.dsi.ant.exception.AntInterfaceException;
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -135,7 +135,7 @@ public class AntDirectSensorManager extends AntSensorManager {
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(context.getString(R.string.ant_heart_rate_sensor_id_key), deviceNumberHRM);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
private void handleMessageResponse(byte[] rawMessage) {
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ import com.dsi.ant.AntMesg;
|
||||
import com.dsi.ant.exception.AntInterfaceException;
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -199,7 +199,7 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(context.getString(R.string.ant_srm_bridge_sensor_id_key), deviceNumber);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
private void handleMessageResponse(byte[] rawMessage) {
|
||||
|
||||
+3
-23
@@ -15,10 +15,9 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services.tasks;
|
||||
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
|
||||
/**
|
||||
* Factory which wraps construction and setup of text-to-speech announcements in
|
||||
@@ -28,30 +27,11 @@ import android.media.AudioManager;
|
||||
*/
|
||||
public class StatusAnnouncerFactory implements PeriodicTaskFactory {
|
||||
|
||||
private final boolean hasTts;
|
||||
|
||||
public StatusAnnouncerFactory(ApiFeatures apiFeatures) {
|
||||
this.hasTts = apiFeatures.hasTextToSpeech();
|
||||
public StatusAnnouncerFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeriodicTask create(Context context) {
|
||||
if (hasTts) {
|
||||
return ApiFeatures.getInstance().getApiAdapter().getStatusAnnouncerTask(context);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate volume stream for controlling announcement
|
||||
* volume.
|
||||
*/
|
||||
public int getVolumeStream() {
|
||||
if (hasTts) {
|
||||
return StatusAnnouncerTask.getVolumeStream();
|
||||
} else {
|
||||
return AudioManager.USE_DEFAULT_STREAM_TYPE;
|
||||
}
|
||||
return ApiAdapterFactory.getApiAdapter().getStatusAnnouncerTask(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,13 +331,6 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the volume stream to use for controlling announcement volume.
|
||||
*/
|
||||
public static int getVolumeStream() {
|
||||
return TextToSpeech.Engine.DEFAULT_STREAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string to announce the time.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* API level 10 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class Api10Adapter extends Api9Adapter {
|
||||
|
||||
@Override
|
||||
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
|
||||
try {
|
||||
return bluetoothDevice.createInsecureRfcommSocketToServiceRecord(
|
||||
BluetoothConnectionManager.SPP_UUID);
|
||||
} catch (IOException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
}
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
|
||||
};
|
||||
}
|
||||
+28
-43
@@ -15,50 +15,32 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
|
||||
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.http.apache.ApacheHttpTransport;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* API level 3 specific implementation of the {@link ApiLevelAdapter}.
|
||||
* API level 7 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public class ApiLevel3Adapter implements ApiLevelAdapter {
|
||||
public class Api7Adapter implements ApiAdapter {
|
||||
|
||||
@Override
|
||||
public void startForeground(Service service,
|
||||
NotificationManager notificationManager, int id,
|
||||
Notification notification) {
|
||||
setServiceForeground(service, true);
|
||||
|
||||
notificationManager.notify(id, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopForeground(Service service,
|
||||
NotificationManager notificationManager, int id) {
|
||||
setServiceForeground(service, false);
|
||||
if (id != -1) {
|
||||
notificationManager.cancel(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeriodicTask getStatusAnnouncerTask(Context context) {
|
||||
return new StatusAnnouncerTask(context);
|
||||
@@ -74,24 +56,6 @@ public class ApiLevel3Adapter implements ApiLevelAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
private void setServiceForeground(Service service, boolean foreground) {
|
||||
// setForeground has been completely removed in API level 11, so we use reflection.
|
||||
try {
|
||||
Method setForegroundMethod = Service.class.getMethod("setForeground", boolean.class);
|
||||
setForegroundMethod.invoke(service, foreground);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Unable to set service foreground state", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(TAG, "Unable to set service foreground state", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Unable to set service foreground state", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(TAG, "Unable to set service foreground state", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(TAG, "Unable to set service foreground state", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPreferenceChanges(Editor editor) {
|
||||
editor.commit();
|
||||
@@ -114,4 +78,25 @@ public class ApiLevel3Adapter implements ApiLevelAdapter {
|
||||
public HttpTransport getHttpTransport() {
|
||||
return new ApacheHttpTransport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
|
||||
try {
|
||||
Class<? extends BluetoothDevice> c = bluetoothDevice.getClass();
|
||||
Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class);
|
||||
insecure.setAccessible(true);
|
||||
return (BluetoothSocket) insecure.invoke(bluetoothDevice, 1);
|
||||
} catch (SecurityException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
}
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
|
||||
}
|
||||
}
|
||||
+17
-2
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||
@@ -8,11 +23,11 @@ import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* API level 8 specific implementation of the {@link ApiLevelAdapter}.
|
||||
* API level 8 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class ApiLevel8Adapter extends ApiLevel5Adapter {
|
||||
public class Api8Adapter extends Api7Adapter {
|
||||
|
||||
@Override
|
||||
public PeriodicTask getStatusAnnouncerTask(Context context) {
|
||||
+17
-2
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
@@ -11,11 +26,11 @@ import android.util.Log;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* API level 9 specific implementation of the {@link ApiLevelAdapter}.
|
||||
* API level 9 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ApiLevel9Adapter extends ApiLevel8Adapter {
|
||||
public class Api9Adapter extends Api8Adapter {
|
||||
|
||||
@Override
|
||||
public void applyPreferenceChanges(Editor editor) {
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A set of methods that may be implemented differently depending on the Android
|
||||
* API level.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public interface ApiAdapter {
|
||||
|
||||
/**
|
||||
* Gets a status announcer task.
|
||||
* <p>
|
||||
* Due to changes in API level 8.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public PeriodicTask getStatusAnnouncerTask(Context context);
|
||||
|
||||
/**
|
||||
* Gets a {@link BackupPreferencesListener}.
|
||||
* <p>
|
||||
* Due to changes in API level 8.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public BackupPreferencesListener getBackupPreferencesListener(Context context);
|
||||
|
||||
/**
|
||||
* Applies all the changes done to a given preferences editor. Changes may or
|
||||
* may not be applied immediately.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*
|
||||
* @param editor the editor
|
||||
*/
|
||||
public void applyPreferenceChanges(SharedPreferences.Editor editor);
|
||||
|
||||
/**
|
||||
* Enables strict mode where supported, only if this is a development build.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
public void enableStrictMode();
|
||||
|
||||
/**
|
||||
* Copies elements from an input byte array into a new byte array, from
|
||||
* indexes start (inclusive) to end (exclusive). The end index must be less
|
||||
* than or equal to the input length.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*
|
||||
* @param input the input byte array
|
||||
* @param start the start index
|
||||
* @param end the end index
|
||||
* @return a new array containing elements from the input byte array.
|
||||
*/
|
||||
public byte[] copyByteArray(byte[] input, int start, int end);
|
||||
|
||||
/**
|
||||
* Gets a {@link HttpTransport}.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
public HttpTransport getHttpTransport();
|
||||
|
||||
/**
|
||||
* Gets a {@link BluetoothSocket}.
|
||||
* <p>
|
||||
* Due to changes in API level 10.
|
||||
*
|
||||
* @param bluetoothDevice
|
||||
*/
|
||||
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* A factory to get the {@link ApiAdapter} for the current device.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ApiAdapterFactory {
|
||||
|
||||
private static ApiAdapter apiAdapter;
|
||||
|
||||
/**
|
||||
* Gets the {@link ApiAdapter} for the current device.
|
||||
*/
|
||||
public static ApiAdapter getApiAdapter() {
|
||||
if (apiAdapter == null) {
|
||||
if (Build.VERSION.SDK_INT >= 10) {
|
||||
apiAdapter = new Api10Adapter();
|
||||
return apiAdapter;
|
||||
} else if (Build.VERSION.SDK_INT >= 9) {
|
||||
apiAdapter = new Api9Adapter();
|
||||
return apiAdapter;
|
||||
} else if (Build.VERSION.SDK_INT >= 8) {
|
||||
apiAdapter = new Api8Adapter();
|
||||
return apiAdapter;
|
||||
} else {
|
||||
apiAdapter = new Api7Adapter();
|
||||
return apiAdapter;
|
||||
}
|
||||
}
|
||||
return apiAdapter;
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Utility class for determining if newer-API features are available on the
|
||||
* current device.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ApiFeatures {
|
||||
|
||||
/**
|
||||
* The API level of the Android version we are being run under.
|
||||
*/
|
||||
private static final int ANDROID_API_LEVEL = Integer.parseInt(
|
||||
Build.VERSION.SDK);
|
||||
|
||||
private static ApiFeatures instance;
|
||||
|
||||
/**
|
||||
* The API level adapter for the Android version we are being run under.
|
||||
*/
|
||||
private ApiLevelAdapter apiLevelAdapter;
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of this class.
|
||||
*/
|
||||
public static ApiFeatures getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ApiFeatures();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a specific singleton instance, to be used for unit tests.
|
||||
*/
|
||||
@SuppressWarnings("hiding")
|
||||
public static void injectInstance(ApiFeatures instance) {
|
||||
ApiFeatures.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow subclasses for mocking, but no direct instantiation.
|
||||
*/
|
||||
protected ApiFeatures() {
|
||||
// It is safe to import unsupported classes as long as we only actually
|
||||
// load the class when supported.
|
||||
if (getApiLevel() >= 9) {
|
||||
apiLevelAdapter = new ApiLevel9Adapter();
|
||||
} else if (getApiLevel() >= 8) {
|
||||
apiLevelAdapter = new ApiLevel8Adapter();
|
||||
} else if (getApiLevel() >= 5) {
|
||||
apiLevelAdapter = new ApiLevel5Adapter();
|
||||
} else {
|
||||
apiLevelAdapter = new ApiLevel3Adapter();
|
||||
}
|
||||
|
||||
Log.i(Constants.TAG, "Using API level adapter " + apiLevelAdapter.getClass());
|
||||
}
|
||||
|
||||
public ApiLevelAdapter getApiAdapter() {
|
||||
return apiLevelAdapter;
|
||||
}
|
||||
|
||||
// API Level 4 Changes
|
||||
|
||||
/**
|
||||
* Returns whether text-to-speech is available.
|
||||
*/
|
||||
public boolean hasTextToSpeech() {
|
||||
return getApiLevel() >= 4;
|
||||
}
|
||||
|
||||
// API Level 5 Changes
|
||||
|
||||
/**
|
||||
* There's a bug (#1587) in Cupcake and Donut which prevents you from
|
||||
* using a SQLiteQueryBuilder twice. That is, if you call buildQuery
|
||||
* on a given instance (to log the statement for debugging), and then
|
||||
* call query on the same instance to make it actually do the query,
|
||||
* it'll regenerate the query for the second call, and will screw it
|
||||
* up. Specifically, it'll add extra parens which don't belong.
|
||||
*/
|
||||
public boolean canReuseSQLiteQueryBuilder() {
|
||||
return getApiLevel() >= 5;
|
||||
}
|
||||
|
||||
// API Level 10 changes
|
||||
|
||||
/**
|
||||
* Returns true if BluetoothDevice.createInsecureRfcommSocketToServiceRecord
|
||||
* is available.
|
||||
*/
|
||||
public boolean hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord() {
|
||||
return getApiLevel() >= 10;
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
protected int getApiLevel() {
|
||||
return ANDROID_API_LEVEL;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
/**
|
||||
* A set of methods that may be implemented differently depending on the Android API level.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public interface ApiLevelAdapter {
|
||||
|
||||
/**
|
||||
* Puts the specified service into foreground.
|
||||
*
|
||||
* Due to changes in API level 5.
|
||||
*
|
||||
* @param service the service to be put in foreground.
|
||||
* @param notificationManager the notification manager used to post the given
|
||||
* notification.
|
||||
* @param id the ID of the notification, unique within the application.
|
||||
* @param notification the notification to post.
|
||||
*/
|
||||
void startForeground(Service service, NotificationManager notificationManager,
|
||||
int id, Notification notification);
|
||||
|
||||
/**
|
||||
* Puts the given service into background.
|
||||
*
|
||||
* Due to changes in API level 5.
|
||||
*
|
||||
* @param service the service to put into background.
|
||||
* @param notificationManager the notification manager to user when removing
|
||||
* notifications.
|
||||
* @param id the ID of the notification to be remove, or -1 if the
|
||||
* notification shouldn't be removed.
|
||||
*/
|
||||
void stopForeground(Service service, NotificationManager notificationManager,
|
||||
int id);
|
||||
|
||||
/**
|
||||
* Gets a status announcer task.
|
||||
*
|
||||
* Due to changes in API level 8.
|
||||
*/
|
||||
PeriodicTask getStatusAnnouncerTask(Context context);
|
||||
|
||||
/**
|
||||
* Gets a {@link BackupPreferencesListener}.
|
||||
*
|
||||
* Due to changes in API level 8.
|
||||
*/
|
||||
BackupPreferencesListener getBackupPreferencesListener(Context context);
|
||||
|
||||
/**
|
||||
* Applies all changes done to the given preferences editor.
|
||||
* Changes may or may not be applied immediately.
|
||||
*
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
void applyPreferenceChanges(SharedPreferences.Editor editor);
|
||||
|
||||
/**
|
||||
* Enables strict mode where supported, only if this is a development build.
|
||||
*
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
void enableStrictMode();
|
||||
|
||||
/**
|
||||
* Copies elements from the input byte array into a new byte array, from
|
||||
* indexes start (inclusive) to end (exclusive). The end index must be less
|
||||
* than or equal to input.length.
|
||||
*
|
||||
* Due to changes in API level 9.
|
||||
*
|
||||
* @param input the input byte array
|
||||
* @param start the start index
|
||||
* @param end the end index
|
||||
* @return a new array containing elements from the input byte array
|
||||
*/
|
||||
byte[] copyByteArray(byte[] input, int start, int end);
|
||||
|
||||
|
||||
/**
|
||||
* Gets a {@link HttpTransport}.
|
||||
*
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
HttpTransport getHttpTransport();
|
||||
}
|
||||
@@ -15,186 +15,49 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with bluetooth devices.
|
||||
* This can be used safely even in systems that don't support bluetooth,
|
||||
* in which case a dummy implementation will be used.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public abstract class BluetoothDeviceUtils {
|
||||
public static final String ANY_DEVICE = "any";
|
||||
private static BluetoothDeviceUtils instance;
|
||||
public class BluetoothDeviceUtils {
|
||||
|
||||
private BluetoothDeviceUtils() {}
|
||||
|
||||
/**
|
||||
* Dummy implementation, for systems that don't support bluetooth.
|
||||
*/
|
||||
private static class DummyImpl extends BluetoothDeviceUtils {
|
||||
@Override
|
||||
public void populateDeviceLists(List<String> deviceNames, List<String> deviceAddresses) {
|
||||
// Do nothing - no devices to add
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothDevice findDeviceMatching(String targetDeviceAddress) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Real implementation, for systems that DO support bluetooth.
|
||||
*/
|
||||
private static class RealImpl extends BluetoothDeviceUtils {
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
|
||||
public RealImpl() {
|
||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
if (bluetoothAdapter == null) {
|
||||
throw new IllegalStateException("Unable to get bluetooth adapter");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateDeviceLists(List<String> deviceNames, List<String> deviceAddresses) {
|
||||
ensureNotDiscovering();
|
||||
|
||||
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
||||
for (BluetoothDevice device : pairedDevices) {
|
||||
BluetoothClass bluetoothClass = device.getBluetoothClass();
|
||||
if (bluetoothClass != null) {
|
||||
// Not really sure what we want, but I know what we don't want.
|
||||
switch(bluetoothClass.getMajorDeviceClass()) {
|
||||
case BluetoothClass.Device.Major.COMPUTER:
|
||||
case BluetoothClass.Device.Major.PHONE:
|
||||
break;
|
||||
default:
|
||||
deviceAddresses.add(device.getAddress());
|
||||
deviceNames.add(device.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothDevice findDeviceMatching(String targetDeviceAddress) {
|
||||
if (targetDeviceAddress.equals(ANY_DEVICE)) {
|
||||
return findAnyDevice();
|
||||
} else {
|
||||
return findDeviceByAddress(targetDeviceAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the first suitable bluetooth sensor.
|
||||
*/
|
||||
private BluetoothDevice findAnyDevice() {
|
||||
ensureNotDiscovering();
|
||||
|
||||
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
||||
for (BluetoothDevice device : pairedDevices) {
|
||||
// Look for the first paired computer device
|
||||
if (isSuitableDevice(device)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns a device with the given address, or null if it's not
|
||||
* a suitable sensor.
|
||||
*/
|
||||
private BluetoothDevice findDeviceByAddress(String targetDeviceAddress) {
|
||||
ensureNotDiscovering();
|
||||
|
||||
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(targetDeviceAddress);
|
||||
if (isSuitableDevice(device)) {
|
||||
return device;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the bluetooth adapter is not in discovery mode.
|
||||
*/
|
||||
private void ensureNotDiscovering() {
|
||||
// If it's in discovery mode, cancel that for now.
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given device is a suitable sensor.
|
||||
*
|
||||
* @param device the device to check
|
||||
* @return true if it's suitable, false otherwise
|
||||
*/
|
||||
private boolean isSuitableDevice(BluetoothDevice device) {
|
||||
// Check that the device is bonded
|
||||
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the proper (singleton) instance of this class.
|
||||
*/
|
||||
public static BluetoothDeviceUtils getInstance() {
|
||||
if (instance == null) {
|
||||
if (!isBluetoothMethodSupported()) {
|
||||
Log.d(Constants.TAG, "Using dummy bluetooth utils");
|
||||
instance = new DummyImpl();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Using real bluetooth utils");
|
||||
try {
|
||||
instance = new RealImpl();
|
||||
} catch (IllegalStateException ise) {
|
||||
Log.w(Constants.TAG, "Oops, I mean, using dummy bluetooth utils", ise);
|
||||
instance = new DummyImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given lists with the names and addresses of all suitable
|
||||
* Populates the device names and the device addresses with all the suitable
|
||||
* bluetooth devices.
|
||||
*
|
||||
* @param deviceNames the list to populate with user-visible names
|
||||
* @param deviceAddresses the list to populate with device addresses
|
||||
* @param bluetoothAdapter the bluetooth adapter
|
||||
* @param deviceNames list of device names
|
||||
* @param deviceAddresses list of device addresses
|
||||
*/
|
||||
public abstract void populateDeviceLists(List<String> deviceNames, List<String> deviceAddresses);
|
||||
public static void populateDeviceLists(
|
||||
BluetoothAdapter bluetoothAdapter, List<String> deviceNames, List<String> deviceAddresses) {
|
||||
// Ensure the bluetooth adapter is not in discovery mode.
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
|
||||
/**
|
||||
* Finds the bluetooth device with the given address.
|
||||
*
|
||||
* @param targetDeviceAddress the address of the device, or
|
||||
* {@link #ANY_DEVICE} for using the first suitable device
|
||||
* @return the device's descriptor, or null if not found
|
||||
*/
|
||||
public abstract BluetoothDevice findDeviceMatching(String targetDeviceAddress);
|
||||
|
||||
/**
|
||||
* @return whether the bluetooth method is supported on this device
|
||||
*/
|
||||
public static boolean isBluetoothMethodSupported() {
|
||||
return Integer.parseInt(Build.VERSION.SDK) >= 5;
|
||||
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
||||
for (BluetoothDevice device : pairedDevices) {
|
||||
BluetoothClass bluetoothClass = device.getBluetoothClass();
|
||||
if (bluetoothClass != null) {
|
||||
// Not really sure what we want, but I know what we don't want.
|
||||
switch (bluetoothClass.getMajorDeviceClass()) {
|
||||
case BluetoothClass.Device.Major.COMPUTER:
|
||||
case BluetoothClass.Device.Major.PHONE:
|
||||
break;
|
||||
default:
|
||||
deviceAddresses.add(device.getAddress());
|
||||
deviceNames.add(device.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class EulaUtil {
|
||||
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
|
||||
Editor editor = preferences.edit();
|
||||
editor.putBoolean(EULA_PREFERENCE_KEY, true);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
public static String getEulaMessage(Context context) {
|
||||
|
||||
+33
-19
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -154,7 +154,7 @@ public interface MyTracksProviderUtils {
|
||||
/**
|
||||
* Creates a cursor over the locations in the track points provider which
|
||||
* iterates over a given range of unique ids.
|
||||
* Caller gets to own the returned cursor. Don't forget to close it.
|
||||
* Caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param trackId the id of the track for which to get the points
|
||||
* @param minTrackPointId the minimum id for the track points
|
||||
@@ -168,7 +168,7 @@ public interface MyTracksProviderUtils {
|
||||
|
||||
/**
|
||||
* Creates a cursor over the waypoints of a track.
|
||||
* Caller gets to own the returned cursor. Don't forget to close it.
|
||||
* Caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param trackId the id of the track for which to get the points
|
||||
* @param minWaypointId the minimum id for the track points
|
||||
@@ -178,6 +178,18 @@ public interface MyTracksProviderUtils {
|
||||
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||
int maxWaypoints);
|
||||
|
||||
/**
|
||||
* Creates a cursor over waypoints with the given selection.
|
||||
* Caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param selection a given selection
|
||||
* @param selectionArgs arguments for the given selection
|
||||
* @param order the order in which to return results
|
||||
* @param maxWaypoints the maximum number of waypoints to return
|
||||
* @return a cursor of the selected waypoints
|
||||
*/
|
||||
Cursor getWaypointsCursor(String selection, String[] selectionArgs, String order, int maxWaypoints);
|
||||
|
||||
/**
|
||||
* Finds a track by given unique track id.
|
||||
* Note that the returned track object does not have any track points attached.
|
||||
@@ -188,24 +200,26 @@ public interface MyTracksProviderUtils {
|
||||
* @return a Track object, or null if not found
|
||||
*/
|
||||
Track getTrack(long id);
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all tracks without track points. If no tracks exist, an empty
|
||||
* list will be returned. Use {@link #getLocationIterator(long, long, boolean, LocationFactory)}
|
||||
* to load the track points.
|
||||
*
|
||||
*
|
||||
* @return a list of all the recorded tracks
|
||||
*/
|
||||
List<Track> getAllTracks();
|
||||
|
||||
/**
|
||||
* Creates a cursor over the tracks provider with a given selection.
|
||||
* Caller gets to own the returned cursor. Don't forget to close it.
|
||||
* Caller owns the returned cursor and is responsible for closing it.
|
||||
*
|
||||
* @param selection a given selection
|
||||
* @param selecttionArgs parameters for the given selection
|
||||
* @param order the order to return results in
|
||||
* @return a cursor of the selected tracks
|
||||
*/
|
||||
Cursor getTracksCursor(String selection);
|
||||
Cursor getTracksCursor(String selection, String[] selectionArgs, String order);
|
||||
|
||||
/**
|
||||
* Inserts a track in the tracks provider.
|
||||
@@ -288,7 +302,7 @@ public interface MyTracksProviderUtils {
|
||||
|
||||
/**
|
||||
* Fill a location object with values from a given cursor.
|
||||
*
|
||||
*
|
||||
* @param cursor a cursor pointing at a db or provider with locations
|
||||
* @param location a location object to be overwritten
|
||||
*/
|
||||
@@ -308,7 +322,7 @@ public interface MyTracksProviderUtils {
|
||||
interface LocationIterator extends Iterator<Location> {
|
||||
/**
|
||||
* Returns ID of the most recently retrieved track point through a call to {@link #next()}.
|
||||
*
|
||||
*
|
||||
* @return the ID of the most recent track point ID.
|
||||
*/
|
||||
long getLocationId();
|
||||
@@ -328,12 +342,12 @@ public interface MyTracksProviderUtils {
|
||||
* Creates a new {@link Location} object to be populated from the underlying database record.
|
||||
* It's up to the implementing class to decide whether to create a new instance or reuse
|
||||
* existing to optimize for speed.
|
||||
*
|
||||
*
|
||||
* @return a {@link Location} to be populated from the database.
|
||||
*/
|
||||
Location createLocation();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The default {@class Location}s factory, which creates a new location of 'gps' type.
|
||||
*/
|
||||
@@ -361,20 +375,20 @@ public interface MyTracksProviderUtils {
|
||||
return locs[lastLoc];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new read-only iterator over all track points for the given track. It provides
|
||||
* a lightweight way of iterating over long tracks without failing due to the underlying cursor
|
||||
* limitations. Since it's a read-only iterator, {@link Iterator#remove()} always throws
|
||||
* {@class UnsupportedOperationException}.
|
||||
*
|
||||
*
|
||||
* Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so,
|
||||
* the iterator calls {@link LocationFactory#createLocation()} and populates it with information
|
||||
* retrieved from the record.
|
||||
*
|
||||
*
|
||||
* When done with iteration, you must call {@link LocationIterator#close()} to make sure that all
|
||||
* resources are properly deallocated.
|
||||
*
|
||||
*
|
||||
* Example use:
|
||||
* <code>
|
||||
* ...
|
||||
@@ -389,14 +403,14 @@ public interface MyTracksProviderUtils {
|
||||
* }
|
||||
* ...
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @param trackId the ID of a track to retrieve locations for.
|
||||
* @param startTrackPointId the ID of the first track point to load, or -1 to start from
|
||||
* the first point.
|
||||
* @param descending if true the results will be returned in descending ID
|
||||
* order (latest location first).
|
||||
* @param locationFactory the factory for creating new locations.
|
||||
*
|
||||
*
|
||||
* @return the read-only iterator over the given track's points.
|
||||
*/
|
||||
LocationIterator getLocationIterator(long trackId, long startTrackPointId, boolean descending,
|
||||
|
||||
+25
-14
@@ -16,7 +16,7 @@
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import static com.google.android.apps.mytracks.lib.MyTracksLibConstants.TAG;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
@@ -875,22 +875,34 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
}
|
||||
|
||||
String selection;
|
||||
String[] selectionArgs;
|
||||
if (minWaypointId > 0) {
|
||||
selection = String.format("%s=%d AND %s>=%d",
|
||||
WaypointsColumns.TRACKID, trackId,
|
||||
WaypointsColumns._ID, minWaypointId);
|
||||
selection = String.format("%s = ? AND %s >= ?",
|
||||
WaypointsColumns.TRACKID,
|
||||
WaypointsColumns._ID);
|
||||
selectionArgs = new String[] {
|
||||
Long.toString(trackId),
|
||||
Long.toString(minWaypointId)
|
||||
};
|
||||
} else {
|
||||
selection = String.format("%s=%d",
|
||||
WaypointsColumns.TRACKID, trackId);
|
||||
selection = String.format("%s=?", WaypointsColumns.TRACKID);
|
||||
selectionArgs = new String[] { Long.toString(trackId) };
|
||||
}
|
||||
|
||||
String sortOrder = "_id ASC";
|
||||
return getWaypointsCursor(selection, selectionArgs, null, maxWaypoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getWaypointsCursor(String selection, String[] selectionArgs, String order, int maxWaypoints) {
|
||||
if (order == null) {
|
||||
order = "_id ASC";
|
||||
}
|
||||
if (maxWaypoints > 0) {
|
||||
sortOrder += " LIMIT " + maxWaypoints;
|
||||
order += " LIMIT " + maxWaypoints;
|
||||
}
|
||||
|
||||
return contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
||||
WaypointsColumns.CONTENT_URI, null, selection, selectionArgs, order);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -905,7 +917,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
|
||||
@Override
|
||||
public List<Track> getAllTracks() {
|
||||
Cursor cursor = getTracksCursor(null);
|
||||
Cursor cursor = getTracksCursor(null, null, TracksColumns._ID);
|
||||
ArrayList<Track> tracks = new ArrayList<Track>();
|
||||
if (cursor != null) {
|
||||
tracks.ensureCapacity(cursor.getCount());
|
||||
@@ -923,10 +935,9 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getTracksCursor(String selection) {
|
||||
Cursor cursor = contentResolver.query(
|
||||
TracksColumns.CONTENT_URI, null, selection, null, "_id");
|
||||
return cursor;
|
||||
public Cursor getTracksCursor(String selection, String[] selectionArgs, String order) {
|
||||
return contentResolver.query(
|
||||
TracksColumns.CONTENT_URI, null, selection, selectionArgs, order);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -26,7 +26,7 @@ import android.os.Parcelable;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TripStatistics implements Parcelable {
|
||||
public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* The start time for the trip. This is system time which might not match gps
|
||||
@@ -144,7 +144,7 @@ public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* Gets the time that this track started.
|
||||
*
|
||||
*
|
||||
* @return The number of milliseconds since epoch to the time when this track
|
||||
* started
|
||||
*/
|
||||
@@ -154,7 +154,7 @@ public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* Gets the time that this track stopped.
|
||||
*
|
||||
*
|
||||
* @return The number of milliseconds since epoch to the time when this track
|
||||
* stopped
|
||||
*/
|
||||
@@ -176,7 +176,7 @@ public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* Gets the total distance the user traveled.
|
||||
*
|
||||
*
|
||||
* @return The total distance traveled in meters
|
||||
*/
|
||||
public double getTotalDistance() {
|
||||
@@ -197,7 +197,7 @@ public class TripStatistics implements Parcelable {
|
||||
/**
|
||||
* Gets the the average speed the user traveled when they were actively
|
||||
* moving.
|
||||
*
|
||||
*
|
||||
* @return The average moving speed in m/s
|
||||
*/
|
||||
public double getAverageMovingSpeed() {
|
||||
@@ -206,7 +206,7 @@ public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* Gets the the maximum speed for this track.
|
||||
*
|
||||
*
|
||||
* @return The maximum speed in m/s
|
||||
*/
|
||||
public double getMaxSpeed() {
|
||||
@@ -215,7 +215,7 @@ public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* Gets the moving time.
|
||||
*
|
||||
*
|
||||
* @return The total number of milliseconds the user was moving
|
||||
*/
|
||||
public long getMovingTime() {
|
||||
@@ -225,7 +225,7 @@ public class TripStatistics implements Parcelable {
|
||||
/**
|
||||
* Gets the total elevation gain for this trip. This is calculated as the sum
|
||||
* of all positive differences in the smoothed elevation.
|
||||
*
|
||||
*
|
||||
* @return The elevation gain in meters for this trip
|
||||
*/
|
||||
public double getTotalElevationGain() {
|
||||
@@ -233,39 +233,79 @@ public class TripStatistics implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the leftmost position (lowest longitude) of the track, in signed
|
||||
* decimal degrees.
|
||||
* Returns the leftmost position (lowest longitude) of the track, in signed degrees.
|
||||
*/
|
||||
public double getLeftDegrees() {
|
||||
return longitudeExtremities.getMin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the leftmost position (lowest longitude) of the track, in signed millions of degrees.
|
||||
*/
|
||||
public int getLeft() {
|
||||
return (int) (longitudeExtremities.getMin() * 1E6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rightmost position (highest longitude) of the track, in signed
|
||||
* decimal degrees.
|
||||
* Returns the rightmost position (highest longitude) of the track, in signed degrees.
|
||||
*/
|
||||
public double getRightDegrees() {
|
||||
return longitudeExtremities.getMax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rightmost position (highest longitude) of the track, in signed millions of degrees.
|
||||
*/
|
||||
public int getRight() {
|
||||
return (int) (longitudeExtremities.getMax() * 1E6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bottommost position (lowest latitude) of the track, in meters.
|
||||
* Returns the bottommost position (lowest latitude) of the track, in signed degrees.
|
||||
*/
|
||||
public double getBottomDegrees() {
|
||||
return latitudeExtremities.getMin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bottommost position (lowest latitude) of the track, in signed millions of degrees.
|
||||
*/
|
||||
public int getBottom() {
|
||||
return (int) (latitudeExtremities.getMin() * 1E6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the topmost position (highest latitude) of the track, in meters.
|
||||
* Returns the topmost position (highest latitude) of the track, in signed degrees.
|
||||
*/
|
||||
public double getTopDegrees() {
|
||||
return latitudeExtremities.getMax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the topmost position (highest latitude) of the track, in signed millions of degrees.
|
||||
*/
|
||||
public int getTop() {
|
||||
return (int) (latitudeExtremities.getMax() * 1E6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mean position (center latitude) of the track, in signed degrees.
|
||||
*/
|
||||
public double getMeanLatitude() {
|
||||
return (getBottomDegrees() + getTopDegrees()) / 2.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mean position (center longitude) of the track, in signed degrees.
|
||||
*/
|
||||
public double getMeanLongitude() {
|
||||
return (getLeftDegrees() + getRightDegrees()) / 2.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum elevation seen on this trip. This is calculated from the
|
||||
* smoothed elevation so this can actually be more than the current elevation.
|
||||
*
|
||||
*
|
||||
* @return The smallest elevation reading for this trip in meters
|
||||
*/
|
||||
public double getMinElevation() {
|
||||
@@ -275,7 +315,7 @@ public class TripStatistics implements Parcelable {
|
||||
/**
|
||||
* Gets the maximum elevation seen on this trip. This is calculated from the
|
||||
* smoothed elevation so this can actually be less than the current elevation.
|
||||
*
|
||||
*
|
||||
* @return The largest elevation reading for this trip in meters
|
||||
*/
|
||||
public double getMaxElevation() {
|
||||
@@ -284,7 +324,7 @@ public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* Gets the maximum grade for this trip.
|
||||
*
|
||||
*
|
||||
* @return The maximum grade for this trip as a fraction
|
||||
*/
|
||||
public double getMaxGrade() {
|
||||
@@ -293,7 +333,7 @@ public class TripStatistics implements Parcelable {
|
||||
|
||||
/**
|
||||
* Gets the minimum grade for this trip.
|
||||
*
|
||||
*
|
||||
* @return The minimum grade for this trip as a fraction
|
||||
*/
|
||||
public double getMinGrade() {
|
||||
@@ -443,7 +483,7 @@ public class TripStatistics implements Parcelable {
|
||||
void addMovingTime(long time) {
|
||||
movingTime += time;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Accounts for a new latitude value for the bounding box.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import com.google.android.apps.mytracks.content.SearchEngine.ScoredResult;
|
||||
import com.google.android.apps.mytracks.content.SearchEngine.SearchQuery;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.RenamingDelegatingContext;
|
||||
import android.test.mock.MockContentResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for {@link SearchEngine}.
|
||||
* These are not meant to be quality tests, but instead feature-by-feature tests
|
||||
* (in other words, they don't test the mixing of different score boostings, just
|
||||
* each boosting separately)
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SearchEngineTest extends AndroidTestCase {
|
||||
|
||||
private static final Location HERE = new Location("gps");
|
||||
private static final long NOW = 1234567890000L; // After OLDEST_ALLOWED_TIMESTAMP
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
private SearchEngine engine;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
MockContentResolver mockContentResolver = new MockContentResolver();
|
||||
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
||||
getContext(), getContext(), "test.");
|
||||
MockContext context = new MockContext(mockContentResolver, targetContext);
|
||||
MyTracksProvider provider = new MyTracksProvider();
|
||||
provider.attachInfo(context, null);
|
||||
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
|
||||
setContext(context);
|
||||
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
engine = new SearchEngine(providerUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
providerUtils.deleteAllTracks();
|
||||
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private long insertTrack(String title, String description, String category, double distance, long hoursAgo) {
|
||||
Track track = new Track();
|
||||
track.setName(title);
|
||||
track.setDescription(description);
|
||||
track.setCategory(category);
|
||||
|
||||
TripStatistics stats = track.getStatistics();
|
||||
if (hoursAgo > 0) {
|
||||
// Started twice hoursAgo, so the average time is hoursAgo.
|
||||
stats.setStartTime(NOW - hoursAgo * 1000L * 60L * 60L * 2);
|
||||
stats.setStopTime(NOW);
|
||||
}
|
||||
|
||||
int latitude = (int) ((HERE.getLatitude() + distance) * 1E6);
|
||||
int longitude = (int) ((HERE.getLongitude() + distance) * 1E6);
|
||||
stats.setBounds(latitude, longitude, latitude, longitude);
|
||||
|
||||
Uri uri = providerUtils.insertTrack(track);
|
||||
return ContentUris.parseId(uri);
|
||||
}
|
||||
|
||||
private long insertTrack(String title, String description, String category) {
|
||||
return insertTrack(title, description, category, 0, -1);
|
||||
}
|
||||
|
||||
private long insertTrack(String title, double distance) {
|
||||
return insertTrack(title, "", "", distance, -1);
|
||||
}
|
||||
|
||||
private long insertTrack(String title, long hoursAgo) {
|
||||
return insertTrack(title, "", "", 0.0, hoursAgo);
|
||||
}
|
||||
|
||||
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) {
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setName(title);
|
||||
waypoint.setDescription(description);
|
||||
waypoint.setCategory(category);
|
||||
waypoint.setTrackId(trackId);
|
||||
|
||||
Location location = new Location(HERE);
|
||||
location.setLatitude(location.getLatitude() + distance);
|
||||
location.setLongitude(location.getLongitude() + distance);
|
||||
if (hoursAgo >= 0) {
|
||||
location.setTime(NOW - hoursAgo * 1000L * 60L * 60L);
|
||||
}
|
||||
waypoint.setLocation(location);
|
||||
|
||||
Uri uri = providerUtils.insertWaypoint(waypoint);
|
||||
return ContentUris.parseId(uri);
|
||||
}
|
||||
|
||||
private long insertWaypoint(String title, String description, String category) {
|
||||
return insertWaypoint(title, description, category, 0.0, -1, -1);
|
||||
}
|
||||
|
||||
private long insertWaypoint(String title, double distance) {
|
||||
return insertWaypoint(title, "", "", distance, -1, -1);
|
||||
}
|
||||
|
||||
private long insertWaypoint(String title, long hoursAgo) {
|
||||
return insertWaypoint(title, "", "", 0.0, hoursAgo, -1);
|
||||
}
|
||||
|
||||
private long insertWaypoint(String title, long hoursAgo, long trackId) {
|
||||
return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId);
|
||||
}
|
||||
|
||||
public void testSearchText() {
|
||||
// Insert 7 tracks (purposefully out of result order):
|
||||
// - one which won't match
|
||||
// - one which will match the description
|
||||
// - one which will match the category
|
||||
// - one which will match the title
|
||||
// - one which will match in title and category
|
||||
// - one which will match in title and description
|
||||
// - one which will match in all fields
|
||||
insertTrack("bb", "cc", "dd");
|
||||
long descriptionMatchId = insertTrack("bb", "aa", "cc");
|
||||
long categoryMatchId = insertTrack("bb", "cc", "aa");
|
||||
long titleMatchId = insertTrack("aa", "bb", "cc");
|
||||
long titleCategoryMatchId = insertTrack("aa", "bb", "ca");
|
||||
long titleDescriptionMatchId = insertTrack("aa", "ba", "cc");
|
||||
long allMatchId = insertTrack("aa", "ba", "ca");
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Title > Description > Category.
|
||||
assertTrackResults(results,
|
||||
allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId,
|
||||
categoryMatchId);
|
||||
}
|
||||
|
||||
public void testSearchWaypointText() {
|
||||
// Insert 7 waypoints (purposefully out of result order):
|
||||
// - one which won't match
|
||||
// - one which will match the description
|
||||
// - one which will match the category
|
||||
// - one which will match the title
|
||||
// - one which will match in title and category
|
||||
// - one which will match in title and description
|
||||
// - one which will match in all fields
|
||||
insertWaypoint("bb", "cc", "dd");
|
||||
long descriptionMatchId = insertWaypoint("bb", "aa", "cc");
|
||||
long categoryMatchId = insertWaypoint("bb", "cc", "aa");
|
||||
long titleMatchId = insertWaypoint("aa", "bb", "cc");
|
||||
long titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
|
||||
long titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
|
||||
long allMatchId = insertWaypoint("aa", "ba", "ca");
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Title > Description > Category.
|
||||
assertWaypointResults(results,
|
||||
allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId,
|
||||
categoryMatchId);
|
||||
}
|
||||
|
||||
public void testSearchMixedText() {
|
||||
// Insert 5 entries (purposefully out of result order):
|
||||
// - one waypoint which will match by description
|
||||
// - one waypoint which won't match
|
||||
// - one waypoint which will match by title
|
||||
// - one track which won't match
|
||||
// - one track which will match by title
|
||||
long descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
|
||||
insertWaypoint("bb", "cc", "dd");
|
||||
long titleWaypointId = insertWaypoint("aa", "bb", "cc");
|
||||
insertTrack("bb", "cc", "dd");
|
||||
long trackId = insertTrack("aa", "bb", "cc");
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Title > Description > Category.
|
||||
assertEquals(results.toString(), 3, results.size());
|
||||
assertTrackResult(trackId, results.get(0));
|
||||
assertWaypointResult(titleWaypointId, results.get(1));
|
||||
assertWaypointResult(descriptionWaypointId, results.get(2));
|
||||
}
|
||||
|
||||
public void testSearchTrackDistance() {
|
||||
// All results match text, but they're at difference distances from the user.
|
||||
long farFarAwayId = insertTrack("aa", 0.3);
|
||||
long nearId = insertTrack("ab", 0.1);
|
||||
long farId = insertTrack("ac", 0.2);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Distance order.
|
||||
assertTrackResults(results, nearId, farId, farFarAwayId);
|
||||
}
|
||||
|
||||
public void testSearchWaypointDistance() {
|
||||
// All results match text, but they're at difference distances from the user.
|
||||
long farFarAwayId = insertWaypoint("aa", 0.3);
|
||||
long nearId = insertWaypoint("ab", 0.1);
|
||||
long farId = insertWaypoint("ac", 0.2);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Distance order.
|
||||
assertWaypointResults(results, nearId, farId, farFarAwayId);
|
||||
}
|
||||
|
||||
public void testSearchTrackRecent() {
|
||||
// All results match text, but they're were recorded at different times.
|
||||
long oldestId = insertTrack("aa", 3);
|
||||
long recentId = insertTrack("ab", 1);
|
||||
long oldId = insertTrack("ac", 2);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Reverse time order.
|
||||
assertTrackResults(results, recentId, oldId, oldestId);
|
||||
}
|
||||
|
||||
public void testSearchWaypointRecent() {
|
||||
// All results match text, but they're were recorded at different times.
|
||||
long oldestId = insertWaypoint("aa", 2);
|
||||
long recentId = insertWaypoint("ab", 0);
|
||||
long oldId = insertWaypoint("ac", 1);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Reverse time order.
|
||||
assertWaypointResults(results, recentId, oldId, oldestId);
|
||||
}
|
||||
|
||||
public void testSearchCurrentTrack() {
|
||||
// All results match text, but one of them is the current track.
|
||||
long currentId = insertTrack("ab", 1);
|
||||
long otherId = insertTrack("aa", 1);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, currentId, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Current track should be demoted.
|
||||
assertTrackResults(results, otherId, currentId);
|
||||
}
|
||||
|
||||
public void testSearchCurrentTrackWaypoint() {
|
||||
// All results match text, but one of them is in the current track.
|
||||
long otherId = insertWaypoint("aa", 1, 456);
|
||||
long currentId = insertWaypoint("ab", 1, 123);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, 123, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
|
||||
// Waypoint in current track should be promoted.
|
||||
assertWaypointResults(results, currentId, otherId);
|
||||
}
|
||||
|
||||
private void assertTrackResult(long trackId, ScoredResult result) {
|
||||
assertNotNull("Not a track", result.track);
|
||||
assertNull("Ambiguous result", result.waypoint);
|
||||
assertEquals(trackId, result.track.getId());
|
||||
}
|
||||
|
||||
private void assertTrackResults(List<ScoredResult> results, long... trackIds) {
|
||||
String errMsg = "Expected IDs=" + Arrays.toString(trackIds) + "; results=" + results;
|
||||
assertEquals(results.size(), trackIds.length);
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
ScoredResult result = results.get(i);
|
||||
assertNotNull(errMsg, result.track);
|
||||
assertNull(errMsg, result.waypoint);
|
||||
assertEquals(errMsg, trackIds[i], result.track.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertWaypointResult(long waypointId, ScoredResult result) {
|
||||
assertNotNull("Not a waypoint", result.waypoint);
|
||||
assertNull("Ambiguous result", result.track);
|
||||
assertEquals(waypointId, result.waypoint.getId());
|
||||
}
|
||||
|
||||
private void assertWaypointResults(List<ScoredResult> results, long... waypointIds) {
|
||||
String errMsg = "Expected IDs=" + Arrays.toString(waypointIds) + "; results=" + results;
|
||||
assertEquals(results.size(), waypointIds.length);
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
ScoredResult result = results.get(i);
|
||||
assertNotNull(errMsg, result.waypoint);
|
||||
assertNull(errMsg, result.track);
|
||||
assertEquals(errMsg, waypointIds[i], result.waypoint.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.test.ServiceTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A {@link TrackRecordingService} that can be used with {@link ServiceTestCase}.
|
||||
* {@link ServiceTestCase} throws a null pointer exception when the service
|
||||
* calls {@link Service#startForeground(int, android.app.Notification)} and
|
||||
* {@link Service#stopForeground(boolean)}.
|
||||
* <p>
|
||||
* See http://code.google.com/p/android/issues/detail?id=12122
|
||||
* <p>
|
||||
* Wrap these two methods in wrappers and override them.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class TestRecordingService extends TrackRecordingService {
|
||||
|
||||
private static final String TAG = TestRecordingService.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
protected void startForegroundService(Notification notification) {
|
||||
try {
|
||||
Method setForegroundMethod = Service.class.getMethod("setForeground", boolean.class);
|
||||
setForegroundMethod.invoke(this, true);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopForegroundService() {
|
||||
try {
|
||||
Method setForegroundMethod = Service.class.getMethod("setForeground", boolean.class);
|
||||
setForegroundMethod.invoke(this, false);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(TAG, "Unable to start a service in foreground", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-19
@@ -24,7 +24,6 @@ import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -57,8 +56,7 @@ import java.util.List;
|
||||
* is called, which prevents from testing multiple scenarios in a single
|
||||
* test (see runFunctionTest for more details).
|
||||
*/
|
||||
public class TrackRecordingServiceTest
|
||||
extends ServiceTestCase<TrackRecordingService> {
|
||||
public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingService> {
|
||||
|
||||
private Context context;
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
@@ -74,7 +72,7 @@ public class TrackRecordingServiceTest
|
||||
private Intent serviceIntent;
|
||||
|
||||
public TrackRecordingServiceTest() {
|
||||
super(TrackRecordingService.class);
|
||||
super(TestRecordingService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,19 +94,6 @@ public class TrackRecordingServiceTest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mock class that forces API level < 5 to make sure we can workaround a bug
|
||||
* in ServiceTestCase (throwing a NPE).
|
||||
* See http://code.google.com/p/android/issues/detail?id=12122 for more
|
||||
* details.
|
||||
*/
|
||||
private static class MockApiFeatures extends ApiFeatures {
|
||||
@Override
|
||||
protected int getApiLevel() {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IBinder bindService(Intent intent) {
|
||||
if (getService() != null) {
|
||||
@@ -138,8 +123,6 @@ public class TrackRecordingServiceTest
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
ApiFeatures.injectInstance(new MockApiFeatures());
|
||||
|
||||
MockContentResolver mockContentResolver = new MockContentResolver();
|
||||
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
||||
getContext(), getContext(), "test.");
|
||||
|
||||
+1
-61
@@ -15,13 +15,6 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services.tasks;
|
||||
|
||||
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
|
||||
import android.media.AudioManager;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
/**
|
||||
@@ -31,62 +24,9 @@ import android.test.AndroidTestCase;
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StatusAnnouncerFactoryTest extends AndroidTestCase {
|
||||
/**
|
||||
* Mock version of the {@link ApiFeatures} class.
|
||||
*/
|
||||
private class MockApiFeatures extends ApiFeatures {
|
||||
private boolean hasTts;
|
||||
|
||||
public void setHasTextToSpeech(boolean hasTts) {
|
||||
this.hasTts = hasTts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTextToSpeech() {
|
||||
return hasTts;
|
||||
}
|
||||
}
|
||||
|
||||
private MockApiFeatures apiFeatures;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
apiFeatures = new MockApiFeatures();
|
||||
}
|
||||
|
||||
public void testCreate() {
|
||||
apiFeatures.setHasTextToSpeech(true);
|
||||
PeriodicTaskFactory factory =
|
||||
new StatusAnnouncerFactory(apiFeatures);
|
||||
PeriodicTaskFactory factory = new StatusAnnouncerFactory();
|
||||
PeriodicTask task = factory.create(getContext());
|
||||
assertTrue(task instanceof StatusAnnouncerTask);
|
||||
}
|
||||
|
||||
public void testCreate_notAvailable() {
|
||||
apiFeatures.setHasTextToSpeech(false);
|
||||
PeriodicTaskFactory factory =
|
||||
new StatusAnnouncerFactory(apiFeatures);
|
||||
PeriodicTask task = factory.create(getContext());
|
||||
assertNull(task);
|
||||
}
|
||||
|
||||
public void testGetVolumeStream() {
|
||||
apiFeatures.setHasTextToSpeech(true);
|
||||
StatusAnnouncerFactory factory =
|
||||
new StatusAnnouncerFactory(apiFeatures);
|
||||
assertEquals(
|
||||
TextToSpeech.Engine.DEFAULT_STREAM,
|
||||
factory.getVolumeStream());
|
||||
}
|
||||
|
||||
public void testGetVolumeStream_notAvailable() {
|
||||
apiFeatures.setHasTextToSpeech(false);
|
||||
StatusAnnouncerFactory factory =
|
||||
new StatusAnnouncerFactory(apiFeatures);
|
||||
assertEquals(
|
||||
AudioManager.USE_DEFAULT_STREAM_TYPE,
|
||||
factory.getVolumeStream());
|
||||
}
|
||||
}
|
||||
|
||||
+6
-18
@@ -15,27 +15,15 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* API level 5 specific implementation of the {@link ApiLevelAdapter}.
|
||||
* Tests {@link ApiAdapterFactory}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ApiLevel5Adapter extends ApiLevel3Adapter {
|
||||
|
||||
@Override
|
||||
public void startForeground(Service service,
|
||||
NotificationManager notificationManager, int id,
|
||||
Notification notification) {
|
||||
service.startForeground(id, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopForeground(Service service,
|
||||
NotificationManager notificationManager, int id) {
|
||||
service.stopForeground(id != -1);
|
||||
public class ApiAdapterFactoryTest extends TestCase {
|
||||
public void testGetApiLevelAdapter() {
|
||||
assertNotNull(ApiAdapterFactory.getApiAdapter());
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Tests the API feature detection code in {@link ApiFeatures}.
|
||||
* This test requires Froyo+ to run.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ApiFeaturesTest extends TestCase {
|
||||
|
||||
private TestableApiFeatures features;
|
||||
|
||||
private class TestableApiFeatures extends ApiFeatures {
|
||||
private int apiLevel;
|
||||
|
||||
public void setApiLevel(int apiLevel) {
|
||||
this.apiLevel = apiLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getApiLevel() {
|
||||
return apiLevel;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
features = new TestableApiFeatures();
|
||||
}
|
||||
|
||||
public void testHasTextToSpeech() {
|
||||
features.setApiLevel(3);
|
||||
assertFalse(features.hasTextToSpeech());
|
||||
for (int i = 4; i <= 8; i++) {
|
||||
features.setApiLevel(i);
|
||||
assertTrue(features.hasTextToSpeech());
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetApiAdapter() {
|
||||
assertNotNull(features.getApiAdapter());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user