forked from upstream-mirrors/OpenTracks
Merging changes from realtime branch into mainline.
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2009 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.services;
|
||||
|
||||
/**
|
||||
* This is a simple location listener policy that will always dictate the same
|
||||
* polling interval.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class AbsoluteLocationListenerPolicy implements LocationListenerPolicy {
|
||||
|
||||
/**
|
||||
* The interval to request for gps signal.
|
||||
*/
|
||||
private final long interval;
|
||||
|
||||
/**
|
||||
* @param interval The interval to request for gps signal
|
||||
*/
|
||||
public AbsoluteLocationListenerPolicy(final long interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The interval given in the constructor
|
||||
*/
|
||||
public long getDesiredPollingInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the idle time.
|
||||
*/
|
||||
public void updateIdleTime(long idleTime) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum distance between updates.
|
||||
* Get all updates to properly measure moving time.
|
||||
*/
|
||||
public int getMinDistance() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2009 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.services;
|
||||
|
||||
/**
|
||||
* A LocationListenerPolicy that will change based on how long the user has been
|
||||
* stationary.
|
||||
*
|
||||
* This policy will dictate a policy based on a min, max and idle time.
|
||||
* The policy will dictate an interval bounded by min and max whic is half of
|
||||
* the idle time.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class AdaptiveLocationListenerPolicy implements LocationListenerPolicy {
|
||||
|
||||
/**
|
||||
* Smallest interval this policy will dictate, in milliseconds.
|
||||
*/
|
||||
private final long minInterval;
|
||||
|
||||
/**
|
||||
* Largest interval this policy will dictate, in milliseconds.
|
||||
*/
|
||||
private final long maxInterval;
|
||||
|
||||
private final int minDistance;
|
||||
|
||||
/**
|
||||
* The time the user has been at the current location, in milliseconds.
|
||||
*/
|
||||
private long idleTime;
|
||||
|
||||
/**
|
||||
* Creates a policy that will be bounded by the given min and max.
|
||||
*
|
||||
* @param min Smallest interval this policy will dictate, in milliseconds
|
||||
* @param max Largest interval this policy will dictate, in milliseconds
|
||||
*/
|
||||
public AdaptiveLocationListenerPolicy(long min, long max, int minDistance) {
|
||||
this.minInterval = min;
|
||||
this.maxInterval = max;
|
||||
this.minDistance = minDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An interval bounded by min and max which is half of the idle time
|
||||
*/
|
||||
public long getDesiredPollingInterval() {
|
||||
long desiredInterval = idleTime / 2;
|
||||
// Round to avoid setting the interval too often.
|
||||
desiredInterval = (desiredInterval / 1000) * 1000;
|
||||
return Math.max(Math.min(maxInterval, desiredInterval),
|
||||
minInterval);
|
||||
}
|
||||
|
||||
public void updateIdleTime(long newIdleTime) {
|
||||
this.idleTime = newIdleTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum distance between updates.
|
||||
*/
|
||||
public int getMinDistance() {
|
||||
return minDistance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2008 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.services;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
|
||||
/**
|
||||
* MyTracks service.
|
||||
* This service is the process that actually records and manages tracks.
|
||||
*/
|
||||
interface ITrackRecordingService {
|
||||
|
||||
/**
|
||||
* Starts recording a new track.
|
||||
*
|
||||
* @return the track ID of the new track
|
||||
*/
|
||||
long startNewTrack();
|
||||
|
||||
/**
|
||||
* Checks and returns whether we're currently recording a track.
|
||||
*/
|
||||
boolean isRecording();
|
||||
|
||||
/**
|
||||
* Returns the track ID of the track currently being recorded, or -1 if none
|
||||
* is being recorded. This ID can then be used to read track data from the
|
||||
* content source.
|
||||
*/
|
||||
long getRecordingTrackId();
|
||||
|
||||
/**
|
||||
* Inserts a waypoint marker in the track being recorded.
|
||||
*
|
||||
* @param waypoint the waypoint to insert
|
||||
* @return the unique ID of the inserted marker
|
||||
*/
|
||||
long insertWaypointMarker(in Waypoint waypoint);
|
||||
|
||||
/**
|
||||
* Inserts a statistics marker in the track being recorded.
|
||||
*
|
||||
* @param location the location at which to insert the marker
|
||||
* @return the unique ID of the inserted marker
|
||||
*/
|
||||
long insertStatisticsMarker(in Location location);
|
||||
|
||||
/**
|
||||
* Inserts a location in the track being recorded.
|
||||
*
|
||||
* When recording, locations detected by the GPS are already automatically
|
||||
* added to the track, so this should be used only for adding special points
|
||||
* or for testing.
|
||||
*
|
||||
* @param loc the location to insert
|
||||
*/
|
||||
void recordLocation(in Location loc);
|
||||
|
||||
/**
|
||||
* Stops recording the current track.
|
||||
*/
|
||||
void endCurrentTrack();
|
||||
|
||||
/**
|
||||
* Returns whether at least one track has been or is being recorded (and was
|
||||
* not deleted).
|
||||
*/
|
||||
boolean hasRecorded();
|
||||
|
||||
/**
|
||||
* Deletes all the stored tracks.
|
||||
*/
|
||||
void deleteAllTracks();
|
||||
|
||||
/**
|
||||
* Notifies the service that its preferences may have been changed.
|
||||
* This is necessary because the service running on a separate process cannot
|
||||
* listen to the changes itself.
|
||||
*
|
||||
* @param key the preference key which may have changed
|
||||
*/
|
||||
void sharedPreferenceChanged(in String key);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2009 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.services;
|
||||
|
||||
/**
|
||||
* This is an interface for classes that will manage the location listener policy.
|
||||
* Different policy options are:
|
||||
* Absolute
|
||||
* Addaptive
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public interface LocationListenerPolicy {
|
||||
|
||||
/**
|
||||
* Returns the polling time this policy would like at this time.
|
||||
*
|
||||
* @return The polling that this policy dictates
|
||||
*/
|
||||
public long getDesiredPollingInterval();
|
||||
|
||||
/**
|
||||
* Returns the minimum distance between updates.
|
||||
*/
|
||||
public int getMinDistance();
|
||||
|
||||
/**
|
||||
* Notifies the amount of time the user has been idle at their current
|
||||
* location.
|
||||
*
|
||||
* @param idleTime The time that the user has been idle at this spot
|
||||
*/
|
||||
public void updateIdleTime(long idleTime);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2009 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.services;
|
||||
|
||||
/**
|
||||
* This is interface for a task that will be executed on some schedule.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public interface PeriodicTask {
|
||||
/**
|
||||
* Sets up this task for subsequent calls to the run method.
|
||||
*/
|
||||
public void start();
|
||||
|
||||
/**
|
||||
* This method will be called periodically.
|
||||
*/
|
||||
public void run(TrackRecordingService service);
|
||||
|
||||
/**
|
||||
* Shuts down this task and clean up resources.
|
||||
*/
|
||||
public void shutdown();
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2009 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.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* This class will periodically announce the user's trip statitics.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class PeriodicTaskExecuter {
|
||||
|
||||
private final TrackRecordingService service;
|
||||
|
||||
/**
|
||||
* A timer to schedule the announcements.
|
||||
*/
|
||||
private Timer timer = new Timer();
|
||||
|
||||
private final PeriodicTask task;
|
||||
|
||||
public PeriodicTaskExecuter(PeriodicTask task,
|
||||
TrackRecordingService service) {
|
||||
this.task = task;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the task at the given interval.
|
||||
*
|
||||
* @param interval The interval in milliseconds
|
||||
*/
|
||||
public void scheduleTask(long interval) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = new Timer();
|
||||
if (interval <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long next = service.getTripStatistics().getStartTime();
|
||||
while (next < now) next += interval;
|
||||
|
||||
Date start = new Date(next);
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"StatusAnnouncer scheduled to start at " + start + " every "
|
||||
+ interval + " milliseconds.");
|
||||
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up this object.
|
||||
*/
|
||||
public void shutdown() {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
task.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* The timer task to announce the trip status.
|
||||
*/
|
||||
private class PeriodicTimerTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
task.run(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2009 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.services;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* This class will periodically announce the user's trip statitics. This class
|
||||
* is a wrapper for cupcake devices.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SafeStatusAnnouncerTask implements PeriodicTask {
|
||||
|
||||
private StatusAnnouncerTask announcer;
|
||||
|
||||
/* class initialization fails when this throws an exception */
|
||||
static {
|
||||
try {
|
||||
Class.forName(
|
||||
"com.google.android.apps.mytracks.services.StatusAnnouncerTask");
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} catch (LinkageError er) {
|
||||
throw new RuntimeException(er);
|
||||
}
|
||||
}
|
||||
|
||||
/* calling here forces class initialization */
|
||||
public static void checkAvailable() {
|
||||
}
|
||||
|
||||
public SafeStatusAnnouncerTask(Context context) {
|
||||
announcer = new StatusAnnouncerTask(context);
|
||||
}
|
||||
|
||||
public void run(TrackRecordingService service) {
|
||||
announcer.run(service);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
announcer.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.telephony.CellLocation;
|
||||
import android.telephony.NeighboringCellInfo;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class to monitor the network signal strength.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SignalStrengthTask extends PhoneStateListener
|
||||
implements PeriodicTask {
|
||||
|
||||
private final Context context;
|
||||
private TelephonyManager manager;
|
||||
private int signalStrength = -1;
|
||||
|
||||
public SignalStrengthTask(Context c) {
|
||||
context = c;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
manager =
|
||||
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
if (manager == null) {
|
||||
Log.e(MyTracksConstants.TAG, "Cannot get telephony manager.");
|
||||
} else {
|
||||
manager.listen(this, getListenEvents());
|
||||
}
|
||||
}
|
||||
|
||||
protected int getListenEvents() {
|
||||
return PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignalStrengthChanged(int asu) {
|
||||
this.signalStrength = asu;
|
||||
Log.d(MyTracksConstants.TAG, "Signal Strength: " + signalStrength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TrackRecordingService service) {
|
||||
Log.d(MyTracksConstants.TAG, "Adding signal marker");
|
||||
Location location = service.getLastLocation();
|
||||
if (manager == null) {
|
||||
Log.d(MyTracksConstants.TAG, "Adding signal marker: marker null");
|
||||
return;
|
||||
}
|
||||
if (location == null) {
|
||||
Log.d(MyTracksConstants.TAG, "Adding signal marker: location null");
|
||||
return;
|
||||
}
|
||||
Waypoint wpt = new Waypoint();
|
||||
wpt.setName("Signal Strength");
|
||||
wpt.setType(Waypoint.TYPE_WAYPOINT);
|
||||
wpt.setTrackId(service.getRecordingTrackId());
|
||||
int networkType = manager.getNetworkType();
|
||||
wpt.setIcon(getIcon(networkType));
|
||||
wpt.setLocation(location);
|
||||
wpt.setDescription(getDescription());
|
||||
long waypointId = service.insertWaypointMarker(wpt);
|
||||
if (waypointId >= 0) {
|
||||
Log.d(MyTracksConstants.TAG, "Added signal marker");
|
||||
} else {
|
||||
Log.e(MyTracksConstants.TAG, "Cannot insert waypoint marker?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a human readable description for the network type.
|
||||
*
|
||||
* @param type The integer constant for the network type
|
||||
* @return A human readable description of the network type
|
||||
*/
|
||||
protected String getTypeAsString(int type) {
|
||||
switch (type) {
|
||||
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||
return "EDGE";
|
||||
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||
return "GPRS";
|
||||
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||
return "UMTS";
|
||||
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a description for the current signal strength.
|
||||
*
|
||||
* @return A human readable description of the network state
|
||||
*/
|
||||
private String getDescription() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(getStrengthAsString());
|
||||
|
||||
sb.append("Network Type: ");
|
||||
sb.append(getTypeAsString(manager.getNetworkType()));
|
||||
sb.append('\n');
|
||||
|
||||
sb.append("Operator: ");
|
||||
sb.append(manager.getNetworkOperatorName());
|
||||
sb.append(" / ");
|
||||
sb.append(manager.getNetworkOperator());
|
||||
sb.append('\n');
|
||||
|
||||
sb.append("Roaming: ");
|
||||
sb.append(manager.isNetworkRoaming());
|
||||
sb.append('\n');
|
||||
|
||||
List<NeighboringCellInfo> infos = manager.getNeighboringCellInfo();
|
||||
Log.i(MyTracksConstants.TAG, "Found " + infos.size() + " cells.");
|
||||
if (infos.size() > 0) {
|
||||
sb.append("Neighbors: ");
|
||||
for (NeighboringCellInfo info : infos) {
|
||||
sb.append(info.toString());
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
CellLocation cell = manager.getCellLocation();
|
||||
if (cell != null) {
|
||||
sb.append("Cell: ");
|
||||
sb.append(cell.toString());
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the url for the waypoint icon for the current network type.
|
||||
*
|
||||
* @param type The network type
|
||||
* @return A url to a image to use as the waypoint icon
|
||||
*/
|
||||
protected String getIcon(int type) {
|
||||
switch (type) {
|
||||
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||
return "http://maps.google.com/mapfiles/ms/micons/green.png";
|
||||
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||
return "http://maps.google.com/mapfiles/ms/micons/blue.png";
|
||||
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
|
||||
default:
|
||||
return "http://maps.google.com/mapfiles/ms/micons/red.png";
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (manager != null) {
|
||||
manager.listen(this, PhoneStateListener.LISTEN_NONE);
|
||||
manager = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getStrengthAsString() {
|
||||
return "Strength: " + signalStrength + "\n";
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.SignalStrength;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A class to monitor the network signal strength.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SignalStrengthTaskModern extends SignalStrengthTask {
|
||||
|
||||
private SignalStrength signalStrength = null;
|
||||
|
||||
public SignalStrengthTaskModern(Context c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getListenEvents() {
|
||||
return PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a human readable description for the network type.
|
||||
*
|
||||
* @param type The integer constant for the network type
|
||||
* @return A human readable description of the network type
|
||||
*/
|
||||
@Override
|
||||
protected String getTypeAsString(int type) {
|
||||
switch (type) {
|
||||
case TelephonyManager.NETWORK_TYPE_1xRTT:
|
||||
return "1xRTT";
|
||||
case TelephonyManager.NETWORK_TYPE_CDMA:
|
||||
return "CDMA";
|
||||
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||
return "EDGE";
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_0:
|
||||
return "EVDO 0";
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_A:
|
||||
return "EVDO A";
|
||||
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||
return "GPRS";
|
||||
case TelephonyManager.NETWORK_TYPE_HSDPA:
|
||||
return "HSDPA";
|
||||
case TelephonyManager.NETWORK_TYPE_HSPA:
|
||||
return "HSPA";
|
||||
case TelephonyManager.NETWORK_TYPE_HSUPA:
|
||||
return "HSUPA";
|
||||
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||
return "UMTS";
|
||||
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the url for the waypoint icon for the current network type.
|
||||
*
|
||||
* @param type The network type
|
||||
* @return A url to a image to use as the waypoint icon
|
||||
*/
|
||||
@Override
|
||||
protected String getIcon(int type) {
|
||||
switch (type) {
|
||||
case TelephonyManager.NETWORK_TYPE_1xRTT:
|
||||
case TelephonyManager.NETWORK_TYPE_CDMA:
|
||||
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||
return "http://maps.google.com/mapfiles/ms/micons/green.png";
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_0:
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_A:
|
||||
case TelephonyManager.NETWORK_TYPE_HSDPA:
|
||||
case TelephonyManager.NETWORK_TYPE_HSPA:
|
||||
case TelephonyManager.NETWORK_TYPE_HSUPA:
|
||||
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||
return "http://maps.google.com/mapfiles/ms/micons/blue.png";
|
||||
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
|
||||
default:
|
||||
return "http://maps.google.com/mapfiles/ms/micons/red.png";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStrengthAsString() {
|
||||
if (signalStrength == null) {
|
||||
return "Strength: " + getContext().getString(R.string.unknown) + "\n";
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (signalStrength.isGsm()) {
|
||||
appendSignal(signalStrength.getGsmSignalStrength(),
|
||||
R.string.gsm_strength,
|
||||
sb);
|
||||
maybeAppendSignal(signalStrength.getGsmBitErrorRate(),
|
||||
R.string.error_rate,
|
||||
sb);
|
||||
} else {
|
||||
appendSignal(signalStrength.getCdmaDbm(), R.string.cdma_strength, sb);
|
||||
appendSignal(signalStrength.getCdmaEcio() / 10.0, R.string.ecio, sb);
|
||||
appendSignal(signalStrength.getEvdoDbm(), R.string.evdo_strength, sb);
|
||||
appendSignal(signalStrength.getEvdoEcio() / 10.0, R.string.ecio, sb);
|
||||
appendSignal(signalStrength.getEvdoSnr(),
|
||||
R.string.signal_to_noise_ratio,
|
||||
sb);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void maybeAppendSignal(
|
||||
int signal, int signalFormat, StringBuffer sb) {
|
||||
if (signal > 0) {
|
||||
sb.append(getContext().getString(signalFormat, signal));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSignal(int signal, int signalFormat, StringBuffer sb) {
|
||||
sb.append(getContext().getString(signalFormat, signal));
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
private void appendSignal(double signal, int signalFormat, StringBuffer sb) {
|
||||
sb.append(getContext().getString(signalFormat, signal));
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
|
||||
Log.d(MyTracksConstants.TAG, "Signal Strength Modern: " + signalStrength);
|
||||
this.signalStrength = signalStrength;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class manages inserting time or distance splits.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SplitManager {
|
||||
|
||||
/**
|
||||
* The frequency of splits.
|
||||
*/
|
||||
private int splitFrequency = 0;
|
||||
|
||||
/**
|
||||
* The next distance when a split should be taken.
|
||||
*/
|
||||
private double nextSplitDistance = 0;
|
||||
|
||||
/**
|
||||
* Splits executer.
|
||||
*/
|
||||
private PeriodicTaskExecuter splitExecuter = null;
|
||||
|
||||
private boolean metricUnits;
|
||||
|
||||
private final TrackRecordingService service;
|
||||
|
||||
public SplitManager(TrackRecordingService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the manager.
|
||||
*/
|
||||
public void restore() {
|
||||
if ((splitFrequency > 0) && (splitExecuter != null)) {
|
||||
splitExecuter.scheduleTask(splitFrequency * 60000);
|
||||
}
|
||||
calculateNextSplit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the manager.
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (splitExecuter != null) {
|
||||
splitExecuter.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next distance that a split should be inserted at.
|
||||
*/
|
||||
public void calculateNextSplit() {
|
||||
if (splitFrequency >= 0) {
|
||||
nextSplitDistance = Double.MAX_VALUE;
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SplitManager: Distance splits dissabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
double dist = service.getTripStatistics().getTotalDistance() / 1000;
|
||||
if (!metricUnits) {
|
||||
dist *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
// The index will be negative since the frequency is negative.
|
||||
int index = (int) (dist / splitFrequency);
|
||||
index -= 1;
|
||||
nextSplitDistance = splitFrequency * index;
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"SplitManager: Next split distance: " + nextSplitDistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates split manager with new trip statistics.
|
||||
*/
|
||||
public void updateSplits() {
|
||||
if (this.splitFrequency >= 0) {
|
||||
return;
|
||||
}
|
||||
// Convert the distance in meters to km or mi.
|
||||
double distance = service.getTripStatistics().getTotalDistance() / 1000.0;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
if (distance > this.nextSplitDistance) {
|
||||
service.insertStatisticsMarker(service.getLastLocation());
|
||||
calculateNextSplit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the split frequency.
|
||||
* < 0 Use the absolute value as a distance in the current measurement km
|
||||
* or mi
|
||||
* 0 Turn off splits
|
||||
* > 0 Use the value as a time in minutes
|
||||
* @param splitFrequency The frequency in time or distance
|
||||
*/
|
||||
public void setSplitFrequency(int splitFrequency) {
|
||||
this.splitFrequency = splitFrequency;
|
||||
if (splitFrequency < 1) {
|
||||
if (splitExecuter != null) {
|
||||
splitExecuter.shutdown();
|
||||
splitExecuter = null;
|
||||
}
|
||||
}
|
||||
if (splitFrequency > 0) {
|
||||
if (splitExecuter == null) {
|
||||
TimeSplitTask splitter = new TimeSplitTask();
|
||||
splitExecuter = new PeriodicTaskExecuter(splitter, service);
|
||||
}
|
||||
splitExecuter.scheduleTask(splitFrequency * 60000);
|
||||
} else {
|
||||
// For distance based splits.
|
||||
calculateNextSplit();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMetricUnits(boolean metricUnits) {
|
||||
this.metricUnits = metricUnits;
|
||||
calculateNextSplit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2009 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.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This class will periodically announce the user's trip statitics.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class StatusAnnouncerTask
|
||||
implements TextToSpeech.OnInitListener, PeriodicTask {
|
||||
|
||||
/**
|
||||
* The rate at which announcements are spoken.
|
||||
*/
|
||||
private static final float TTS_SPEECH_RATE = 0.9f;
|
||||
|
||||
/**
|
||||
* A pointer to the service context.
|
||||
*/
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* String utilities.
|
||||
*/
|
||||
private final StringUtils stringUtils;
|
||||
|
||||
/**
|
||||
* The interface to the text to speech engine.
|
||||
*/
|
||||
private TextToSpeech tts = null;
|
||||
|
||||
/**
|
||||
* The response recieved from the TTS engine ater initialization.
|
||||
*/
|
||||
private int ttsStatus = TextToSpeech.ERROR;
|
||||
|
||||
/**
|
||||
* Constructs the announcer and start the TTS engine.
|
||||
*/
|
||||
public StatusAnnouncerTask(Context context) {
|
||||
this.context = context;
|
||||
this.stringUtils = new StringUtils(context);
|
||||
|
||||
tts = new TextToSpeech(context, this);
|
||||
// Force the language to be the same as the string we will be speaking.
|
||||
tts.setLanguage(Locale.getDefault());
|
||||
// Slow down the speed just a bit as it is hard to hear when exercising.
|
||||
tts.setSpeechRate(TTS_SPEECH_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that the tts engine is done with initialization.
|
||||
*/
|
||||
public void onInit(int status) {
|
||||
this.ttsStatus = status;
|
||||
Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces the trip status.
|
||||
*/
|
||||
@Override
|
||||
public void run(TrackRecordingService service) {
|
||||
if (ttsStatus != TextToSpeech.SUCCESS && tts != null) {
|
||||
Log.e(MyTracksConstants.TAG, "StatusAnnouncer Tts not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (service == null || service.getTripStatistics() == null) {
|
||||
Log.e(MyTracksConstants.TAG, "StatusAnnouncer stats not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
String announcement = getAnnouncement(service.getTripStatistics());
|
||||
Log.d(MyTracksConstants.TAG, "Announcement: " + announcement);
|
||||
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the announcement string.
|
||||
*
|
||||
* @return The string that will be read to the user
|
||||
*/
|
||||
private String getAnnouncement(final TripStatistics stats) {
|
||||
SharedPreferences preferences =
|
||||
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
boolean metricUnits = true;
|
||||
boolean reportSpeed = true;
|
||||
if (preferences != null) {
|
||||
metricUnits =
|
||||
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
||||
true);
|
||||
reportSpeed =
|
||||
preferences.getBoolean(context.getString(R.string.report_speed_key),
|
||||
true);
|
||||
}
|
||||
|
||||
double d = stats.getTotalDistance() / 1000;
|
||||
double s = stats.getAverageMovingSpeed() * 3.6;
|
||||
if (d == 0) {
|
||||
return context.getString(R.string.announce_no_distance);
|
||||
}
|
||||
|
||||
int speedLabel;
|
||||
if (metricUnits) {
|
||||
if (reportSpeed) {
|
||||
speedLabel = R.string.kilometer_per_hour_long;
|
||||
} else {
|
||||
speedLabel = R.string.per_kilometer;
|
||||
}
|
||||
} else {
|
||||
s *= UnitConversions.KMH_TO_MPH;
|
||||
d *= UnitConversions.KM_TO_MI;
|
||||
if (reportSpeed) {
|
||||
speedLabel = R.string.mile_per_hour_long;
|
||||
} else {
|
||||
speedLabel = R.string.per_mile;
|
||||
}
|
||||
}
|
||||
|
||||
String speed = null;
|
||||
if ((s == 0) || Double.isNaN(s)) {
|
||||
speed = context.getString(R.string.unknown);
|
||||
} else {
|
||||
if (reportSpeed) {
|
||||
speed = String.format("%.1f", s);
|
||||
} else {
|
||||
double pace = 3600000.0 / s;
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Converted speed: " + s + " to pace: " + pace);
|
||||
speed = stringUtils.formatTimeLong((long) pace);
|
||||
}
|
||||
}
|
||||
|
||||
return context.getString(R.string.announce_template,
|
||||
context.getString(R.string.total_distance_label),
|
||||
d,
|
||||
context.getString(metricUnits
|
||||
? R.string.kilometers_long
|
||||
: R.string.miles_long),
|
||||
stringUtils.formatTimeLong(stats.getMovingTime()),
|
||||
speed,
|
||||
context.getString(speedLabel));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
tts.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.services;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
|
||||
/**
|
||||
* This class will manage a period task executer.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TaskExecuterManager {
|
||||
|
||||
int frequency;
|
||||
PeriodicTask task;
|
||||
PeriodicTaskExecuter executer;
|
||||
|
||||
public TaskExecuterManager(int frequency,
|
||||
PeriodicTask task,
|
||||
TrackRecordingService service) {
|
||||
this.task = task;
|
||||
setFrequency(frequency, service);
|
||||
}
|
||||
|
||||
public int getFrequency() {
|
||||
return frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the frequency that the task should be run at.
|
||||
* If needed the task will be scheduled.
|
||||
*
|
||||
* @param frequency The frequency in minutes for the task to run
|
||||
* @param service The service to run the task on
|
||||
*/
|
||||
public void setFrequency(int frequency, TrackRecordingService service) {
|
||||
this.frequency = frequency;
|
||||
Log.i(MyTracksConstants.TAG, "Frequency set to: " + frequency);
|
||||
|
||||
if (frequency == -1) {
|
||||
if (executer != null) {
|
||||
executer.shutdown();
|
||||
executer = null;
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"Shut down service: " + task.getClass().getSimpleName());
|
||||
}
|
||||
} else {
|
||||
if (executer == null) {
|
||||
task.start();
|
||||
executer = new PeriodicTaskExecuter(task, service);
|
||||
}
|
||||
executer.scheduleTask(frequency * 60000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the task at the current frequency.
|
||||
*/
|
||||
public void restore() {
|
||||
if (frequency > 0) {
|
||||
task.start();
|
||||
executer.scheduleTask(frequency * 60000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.services;
|
||||
|
||||
/**
|
||||
* A simple task to insert statistics markers every n minutes.
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TimeSplitTask implements PeriodicTask {
|
||||
|
||||
@Override
|
||||
public void run(TrackRecordingService service) {
|
||||
service.insertStatisticsMarker(service.getLastLocation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,986 @@
|
||||
/*
|
||||
* Copyright 2008 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.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracks;
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
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.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* A background service that registers a location listener and records track
|
||||
* points. Track points are saved to the MyTracksProvider.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class TrackRecordingService extends Service implements LocationListener {
|
||||
|
||||
private static final String STATISTICS_ICON_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png";
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private LocationManager locationManager;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
private int minRecordingInterval =
|
||||
MyTracksSettings.DEFAULT_MIN_RECORDING_INTERVAL;
|
||||
private int minRecordingDistance =
|
||||
MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE;
|
||||
private int maxRecordingDistance =
|
||||
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE;
|
||||
private int minRequiredAccuracy =
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
|
||||
private long recordingTrackId = -1;
|
||||
|
||||
private long currentWaypointId = -1;
|
||||
|
||||
/**
|
||||
* For debugging. Keep track of calls to onCreate().
|
||||
*/
|
||||
private boolean onCreateWasCalled = false;
|
||||
|
||||
/** The timer posts a runnable to the main thread via this handler. */
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
/**
|
||||
* Utilities to deal with the database.
|
||||
*/
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
|
||||
private TripStatisticsBuilder statsBuilder = new TripStatisticsBuilder();
|
||||
private TripStatisticsBuilder waypointStatsBuilder = new TripStatisticsBuilder();
|
||||
|
||||
/**
|
||||
* Current length of the recorded track. This length is calculated from the
|
||||
* recorded points (as compared to each location fix). It's used to overlay
|
||||
* waypoints precisely in the elevation profile chart.
|
||||
*/
|
||||
private double length = 0;
|
||||
|
||||
/**
|
||||
* Status announcer executer.
|
||||
*/
|
||||
private PeriodicTaskExecuter executer;
|
||||
private TaskExecuterManager signalManager;
|
||||
private SplitManager splitManager;
|
||||
|
||||
/**
|
||||
* The interval in milliseconds that we have requested to be notified of gps
|
||||
* readings.
|
||||
*/
|
||||
private long currentRecordingInterval = 0;
|
||||
|
||||
/**
|
||||
* The policy used to decide how often we should request gps updates.
|
||||
*/
|
||||
private LocationListenerPolicy locationListenerPolicy =
|
||||
new AbsoluteLocationListenerPolicy(0);
|
||||
|
||||
/**
|
||||
* Task invoked by a timer periodically to make sure the location listener is
|
||||
* still registered.
|
||||
*/
|
||||
private final TimerTask checkLocationListener = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!onCreateWasCalled) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService is running, but onCreate not called.");
|
||||
}
|
||||
if (isRecording) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Re-registering location listener with TrackRecordingService.");
|
||||
unregisterLocationListener();
|
||||
registerLocationListener();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Track recording service is paused. That should not be.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This timer invokes periodically the checkLocationListener timer task.
|
||||
*/
|
||||
private final Timer timer = new Timer();
|
||||
|
||||
/**
|
||||
* Is the phone currently moving?
|
||||
*/
|
||||
private boolean isMoving = true;
|
||||
|
||||
/**
|
||||
* Is the service currently recording a track?
|
||||
*/
|
||||
private boolean isRecording = false;
|
||||
|
||||
/**
|
||||
* Last good location the service has received from the location listener
|
||||
*/
|
||||
private Location lastLocation = null;
|
||||
|
||||
/**
|
||||
* Last valid location (i.e. not a marker) that was recorded.
|
||||
*/
|
||||
private Location lastValidLocation = null;
|
||||
|
||||
/**
|
||||
* The frequency of status announcements.
|
||||
*/
|
||||
private int announcementFrequency = -1;
|
||||
|
||||
private static boolean mTTSAvailable;
|
||||
|
||||
/* establish whether the "new" class is available to us */
|
||||
static {
|
||||
try {
|
||||
SafeStatusAnnouncerTask.checkAvailable();
|
||||
mTTSAvailable = true;
|
||||
} catch (Throwable t) {
|
||||
mTTSAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Inserts a new location in the track points db and updates the corresponding
|
||||
* track in the track db.
|
||||
*
|
||||
* @param recordingTrack the track that is currently being recorded
|
||||
* @param location the location to be inserted
|
||||
* @param lastRecordedLocation the last recorded location before this one (or
|
||||
* null if none)
|
||||
* @param lastRecordedLocationId the id of the last recorded location (or -1
|
||||
* if none)
|
||||
* @param trackId the id of the track
|
||||
* @return true if successful. False if SQLite3 threw an exception.
|
||||
*/
|
||||
private boolean insertLocation(Track recordingTrack, Location location,
|
||||
Location lastRecordedLocation, long lastRecordedLocationId,
|
||||
long trackId) {
|
||||
|
||||
// Keep track of length along recorded track (needed when a waypoint is
|
||||
// inserted):
|
||||
if (MyTracksUtils.isValidLocation(location)) {
|
||||
if (lastValidLocation != null) {
|
||||
length += location.distanceTo(lastValidLocation);
|
||||
}
|
||||
lastValidLocation = location;
|
||||
}
|
||||
|
||||
// Insert the new location:
|
||||
try {
|
||||
Uri pointUri = providerUtils.insertTrackPoint(location, trackId);
|
||||
int pointId = Integer.parseInt(pointUri.getLastPathSegment());
|
||||
|
||||
// Update the current track:
|
||||
if (lastRecordedLocation != null
|
||||
&& lastRecordedLocation.getLatitude() < 90) {
|
||||
ContentValues values = new ContentValues();
|
||||
TripStatistics stats = statsBuilder.getStatistics();
|
||||
if (recordingTrack.getStartId() < 0) {
|
||||
values.put(TracksColumns.STARTID, pointId);
|
||||
recordingTrack.setStartId(pointId);
|
||||
}
|
||||
values.put(TracksColumns.STOPID, pointId);
|
||||
values.put(TracksColumns.STOPTIME, System.currentTimeMillis());
|
||||
values.put(TracksColumns.NUMPOINTS,
|
||||
recordingTrack.getNumberOfPoints() + 1);
|
||||
values.put(TracksColumns.MINLAT, stats.getBottom());
|
||||
values.put(TracksColumns.MAXLAT, stats.getTop());
|
||||
values.put(TracksColumns.MINLON, stats.getLeft());
|
||||
values.put(TracksColumns.MAXLON, stats.getRight());
|
||||
values.put(TracksColumns.TOTALDISTANCE, stats.getTotalDistance());
|
||||
values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
|
||||
values.put(TracksColumns.MOVINGTIME, stats.getMovingTime());
|
||||
values.put(TracksColumns.AVGSPEED, stats.getAverageSpeed());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, stats.getAverageMovingSpeed());
|
||||
values.put(TracksColumns.MAXSPEED, stats.getMaxSpeed());
|
||||
values.put(TracksColumns.MINELEVATION, stats.getMinElevation());
|
||||
values.put(TracksColumns.MAXELEVATION, stats.getMaxElevation());
|
||||
values.put(TracksColumns.ELEVATIONGAIN, stats.getTotalElevationGain());
|
||||
values.put(TracksColumns.MINGRADE, stats.getMinGrade());
|
||||
values.put(TracksColumns.MAXGRADE, stats.getMaxGrade());
|
||||
getContentResolver().update(TracksColumns.CONTENT_URI,
|
||||
values, "_id=" + recordingTrack.getId(), null);
|
||||
updateCurrentWaypoint();
|
||||
}
|
||||
} catch (SQLiteException e) {
|
||||
// Insert failed, most likely because of SqlLite error code 5
|
||||
// (SQLite_BUSY). This is expected to happen extremely rarely (if our
|
||||
// listener gets invoked twice at about the same time).
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Caught SQLiteException: " + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
splitManager.updateSplits();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateCurrentWaypoint() {
|
||||
if (currentWaypointId >= 0) {
|
||||
ContentValues values = new ContentValues();
|
||||
TripStatistics waypointStats = waypointStatsBuilder.getStatistics();
|
||||
values.put(WaypointsColumns.STARTTIME, waypointStats.getStartTime());
|
||||
values.put(WaypointsColumns.LENGTH, length);
|
||||
values.put(WaypointsColumns.DURATION, System.currentTimeMillis()
|
||||
- statsBuilder.getStatistics().getStartTime());
|
||||
values.put(WaypointsColumns.TOTALDISTANCE,
|
||||
waypointStats.getTotalDistance());
|
||||
values.put(WaypointsColumns.TOTALTIME, waypointStats.getTotalTime());
|
||||
values.put(WaypointsColumns.MOVINGTIME, waypointStats.getMovingTime());
|
||||
values.put(WaypointsColumns.AVGSPEED, waypointStats.getAverageSpeed());
|
||||
values.put(WaypointsColumns.AVGMOVINGSPEED,
|
||||
waypointStats.getAverageMovingSpeed());
|
||||
values.put(WaypointsColumns.MAXSPEED, waypointStats.getMaxSpeed());
|
||||
values.put(WaypointsColumns.MINELEVATION,
|
||||
waypointStats.getMinElevation());
|
||||
values.put(WaypointsColumns.MAXELEVATION,
|
||||
waypointStats.getMaxElevation());
|
||||
values.put(WaypointsColumns.ELEVATIONGAIN,
|
||||
waypointStats.getTotalElevationGain());
|
||||
values.put(WaypointsColumns.MINGRADE, waypointStats.getMinGrade());
|
||||
values.put(WaypointsColumns.MAXGRADE, waypointStats.getMaxGrade());
|
||||
getContentResolver().update(WaypointsColumns.CONTENT_URI,
|
||||
values, "_id=" + currentWaypointId, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to acquire a partial wake lock if not already acquired. Logs errors
|
||||
* and gives up trying in case the wake lock cannot be acquired.
|
||||
*/
|
||||
public void acquireWakeLock() {
|
||||
try {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (pm == null) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Power manager not found!");
|
||||
return;
|
||||
}
|
||||
if (wakeLock == null) {
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
MyTracksConstants.TAG);
|
||||
if (wakeLock == null) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Could not create wake lock (null).");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!wakeLock.isHeld()) {
|
||||
wakeLock.acquire();
|
||||
if (!wakeLock.isHeld()) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Could not acquire wake lock.");
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Caught unexpected exception: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the notification message and icon in the notification bar.
|
||||
*/
|
||||
public void showNotification() {
|
||||
if (isRecording) {
|
||||
Notification notification = new Notification(
|
||||
R.drawable.arrow_320, null /* tickerText */,
|
||||
System.currentTimeMillis());
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(
|
||||
this, 0 /* requestCode */, new Intent(this, MyTracks.class),
|
||||
0 /* flags */);
|
||||
notification.setLatestEventInfo(this, getString(R.string.app_name),
|
||||
getString(R.string.recording_your_track), contentIntent);
|
||||
notification.flags += Notification.FLAG_NO_CLEAR;
|
||||
notificationManager.notify(1, notification);
|
||||
} else {
|
||||
notificationManager.cancelAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Do not have any location manager.");
|
||||
return;
|
||||
}
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Preparing to register location listener w/ TrackRecordingService...");
|
||||
try {
|
||||
long desiredInterval = locationListenerPolicy.getDesiredPollingInterval();
|
||||
locationManager.requestLocationUpdates(
|
||||
MyTracksConstants.GPS_PROVIDER, desiredInterval,
|
||||
locationListenerPolicy.getMinDistance(),
|
||||
// , 0 /* minDistance, get all updates to properly time pauses */
|
||||
TrackRecordingService.this);
|
||||
currentRecordingInterval = desiredInterval;
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"...location listener now registered w/ TrackRecordingService @ "
|
||||
+ currentRecordingInterval);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"Could not register location listener: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Do not have any location manager.");
|
||||
return;
|
||||
}
|
||||
locationManager.removeUpdates(this);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Location listener now unregistered w/ TrackRecordingService.");
|
||||
}
|
||||
|
||||
private void restoreStats() {
|
||||
if (recordingTrackId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Track track = providerUtils.getTrack(recordingTrackId);
|
||||
if (track == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics stats = track.getStatistics();
|
||||
statsBuilder = new TripStatisticsBuilder();
|
||||
statsBuilder.resumeAt(stats.getStartTime());
|
||||
if (executer != null) {
|
||||
executer.scheduleTask(announcementFrequency * 60000);
|
||||
}
|
||||
|
||||
signalManager.restore();
|
||||
splitManager.restore();
|
||||
length = 0;
|
||||
lastValidLocation = null;
|
||||
|
||||
Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
|
||||
if (waypoint != null) {
|
||||
currentWaypointId = waypoint.getId();
|
||||
waypointStatsBuilder = new TripStatisticsBuilder(waypoint.getStatistics());
|
||||
} else {
|
||||
// This should never happen, but we got to do something so life goes on:
|
||||
waypointStatsBuilder = new TripStatisticsBuilder();
|
||||
waypointStatsBuilder.resumeAt(stats.getStartTime());
|
||||
currentWaypointId = -1;
|
||||
}
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = providerUtils.getLocationsCursor(
|
||||
recordingTrackId, -1, MyTracksConstants.MAX_LOADED_TRACK_POINTS,
|
||||
true);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToLast()) {
|
||||
do {
|
||||
Location location = providerUtils.createLocation(cursor);
|
||||
if (MyTracksUtils.isValidLocation(location)) {
|
||||
statsBuilder.addLocation(location, location.getTime());
|
||||
if (lastValidLocation != null) {
|
||||
length += location.distanceTo(lastValidLocation);
|
||||
}
|
||||
lastValidLocation = location;
|
||||
}
|
||||
} while (cursor.moveToPrevious());
|
||||
}
|
||||
statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
|
||||
statsBuilder.pauseAt(stats.getStopTime());
|
||||
statsBuilder.resume();
|
||||
} else {
|
||||
Log.e(MyTracksConstants.TAG, "Could not get track points cursor.");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Error while restoring track.", e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
splitManager.calculateNextSplit();
|
||||
}
|
||||
|
||||
/*
|
||||
* Location listener implementation: =================================
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onLocationChanged");
|
||||
|
||||
try {
|
||||
// Don't record if the service has been asked to pause recording:
|
||||
if (!isRecording) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Not recording because recording has been paused.");
|
||||
return;
|
||||
}
|
||||
|
||||
// This should never happen, but just in case (we really don't want the
|
||||
// service to crash):
|
||||
if (location == null) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Location changed, but location is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't record if the accuracy is too bad:
|
||||
if (location.getAccuracy() > minRequiredAccuracy) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Not recording. Bad accuracy.");
|
||||
return;
|
||||
}
|
||||
|
||||
// At least one track must be available for appending points:
|
||||
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
|
||||
if (recordingTrack == null) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Not recording. No track to append to available.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MyTracksUtils.isValidLocation(location)) {
|
||||
long now = System.currentTimeMillis();
|
||||
statsBuilder.addLocation(location, now);
|
||||
waypointStatsBuilder.addLocation(location, now);
|
||||
}
|
||||
|
||||
// Update the idle time if needed.
|
||||
locationListenerPolicy.updateIdleTime(statsBuilder.getIdleTime());
|
||||
if (currentRecordingInterval
|
||||
!= locationListenerPolicy.getDesiredPollingInterval()) {
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
Location lastRecordedLocation = providerUtils.getLastLocation();
|
||||
long lastRecordedLocationId =
|
||||
providerUtils.getLastLocationId(recordingTrackId);
|
||||
double distanceToLastRecorded = Double.POSITIVE_INFINITY;
|
||||
if (lastRecordedLocation != null) {
|
||||
distanceToLastRecorded = location.distanceTo(lastRecordedLocation);
|
||||
}
|
||||
double distanceToLast = Double.POSITIVE_INFINITY;
|
||||
if (lastLocation != null) {
|
||||
distanceToLast = location.distanceTo(lastLocation);
|
||||
}
|
||||
|
||||
// If the user has been stationary for two recording just record the first
|
||||
// two and ignore the rest. This code will only have an effect if the
|
||||
// maxRecordingDistance = 0
|
||||
if (distanceToLast == 0) {
|
||||
if (isMoving) {
|
||||
Log.d(MyTracksConstants.TAG, "Found two identical locations.");
|
||||
isMoving = false;
|
||||
if (lastLocation != null && lastRecordedLocation != null
|
||||
&& !lastRecordedLocation.equals(lastLocation)) {
|
||||
// Need to write the last location. This will happen when
|
||||
// lastRecordedLocation.distance(lastLocation) <
|
||||
// minRecordingDistance
|
||||
if (!insertLocation(recordingTrack, lastLocation,
|
||||
lastRecordedLocation, lastRecordedLocationId,
|
||||
recordingTrackId)) {
|
||||
return;
|
||||
}
|
||||
lastRecordedLocationId++;
|
||||
}
|
||||
} else {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Not recording. More than two identical locations.");
|
||||
}
|
||||
} else if (distanceToLastRecorded > minRecordingDistance) {
|
||||
if (lastLocation != null && !isMoving) {
|
||||
// Last location was the last stationary location. Need to go back and
|
||||
// add it.
|
||||
if (!insertLocation(recordingTrack, lastLocation,
|
||||
lastRecordedLocation, lastRecordedLocationId, recordingTrackId)) {
|
||||
return;
|
||||
}
|
||||
lastRecordedLocationId++;
|
||||
isMoving = true;
|
||||
}
|
||||
|
||||
// If separation from last recorded point is too large insert a
|
||||
// separator to indicate end of a segment:
|
||||
boolean startNewSegment =
|
||||
lastRecordedLocation != null
|
||||
&& lastRecordedLocation.getLatitude() < 90
|
||||
&& distanceToLastRecorded > maxRecordingDistance
|
||||
&& recordingTrack.getStartId() >= 0;
|
||||
if (startNewSegment) {
|
||||
// Insert a separator point to indicate start of new track:
|
||||
Log.d(MyTracksConstants.TAG, "Inserting a separator.");
|
||||
Location separator = new Location(MyTracksConstants.GPS_PROVIDER);
|
||||
separator.setLongitude(0);
|
||||
separator.setLatitude(100);
|
||||
separator.setTime(lastRecordedLocation.getTime());
|
||||
providerUtils.insertTrackPoint(separator, recordingTrackId);
|
||||
}
|
||||
|
||||
if (!insertLocation(recordingTrack, location, lastRecordedLocation,
|
||||
lastRecordedLocationId, recordingTrackId)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.d(MyTracksConstants.TAG, String.format(
|
||||
"Not recording. Distance to last recorded point (%f m) is less than"
|
||||
+ " %d m.", distanceToLastRecorded, minRecordingDistance));
|
||||
}
|
||||
} catch (Error e) {
|
||||
// Probably important enough to rethrow.
|
||||
Log.e(MyTracksConstants.TAG, "Error in onLocationChanged", e);
|
||||
throw e;
|
||||
} catch (RuntimeException e) {
|
||||
// Safe usually to trap exceptions.
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"Trapping exception in onLocationChanged", e);
|
||||
throw e;
|
||||
}
|
||||
lastLocation = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/*
|
||||
* SharedPreferencesChangeListener interface implementation. Note that
|
||||
* services don't currently receive this event (Android platform limitation).
|
||||
* This should be called from an activity whenever settings change.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Notifies that preferences have changed.
|
||||
* Call this with key == null to update all preferences in one call.
|
||||
*
|
||||
* @param key the key that changed (may be null to update all preferences)
|
||||
*/
|
||||
public void onSharedPreferenceChanged(String key) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService.onSharedPreferenceChanged");
|
||||
SharedPreferences sharedPreferences =
|
||||
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
if (sharedPreferences == null) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Couldn't get shared preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == null
|
||||
|| key.equals(getString(R.string.min_recording_distance_key))) {
|
||||
minRecordingDistance = sharedPreferences.getInt(
|
||||
getString(R.string.min_recording_distance_key),
|
||||
MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: minRecordingDistance = "
|
||||
+ minRecordingDistance);
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(getString(R.string.max_recording_distance_key))) {
|
||||
maxRecordingDistance = sharedPreferences.getInt(
|
||||
getString(R.string.max_recording_distance_key),
|
||||
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE);
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(getString(R.string.min_recording_interval_key))) {
|
||||
minRecordingInterval = sharedPreferences.getInt(
|
||||
getString(R.string.min_recording_interval_key),
|
||||
MyTracksSettings.DEFAULT_MIN_RECORDING_INTERVAL);
|
||||
switch (minRecordingInterval) {
|
||||
case -2:
|
||||
// Battery Miser
|
||||
// min: 30 seconds
|
||||
// max: 5 minutes
|
||||
// minDist: 5 meters Choose battery life over moving time accuracy.
|
||||
locationListenerPolicy =
|
||||
new AdaptiveLocationListenerPolicy(30000, 300000, 5);
|
||||
break;
|
||||
case -1:
|
||||
// High Accuracy
|
||||
// min: 1 second
|
||||
// max: 30 seconds
|
||||
// minDist: 0 meters get all updates to properly measure moving time.
|
||||
locationListenerPolicy =
|
||||
new AdaptiveLocationListenerPolicy(1000, 30000, 0);
|
||||
break;
|
||||
default:
|
||||
locationListenerPolicy =
|
||||
new AbsoluteLocationListenerPolicy(minRecordingInterval * 1000);
|
||||
}
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(getString(R.string.min_required_accuracy_key))) {
|
||||
minRequiredAccuracy = sharedPreferences.getInt(
|
||||
getString(R.string.min_required_accuracy_key),
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(getString(R.string.announcement_frequency_key))) {
|
||||
announcementFrequency =
|
||||
sharedPreferences.getInt(getString(R.string.announcement_frequency_key),
|
||||
-1);
|
||||
if (mTTSAvailable) {
|
||||
if (announcementFrequency == -1) {
|
||||
if (executer != null) {
|
||||
executer.shutdown();
|
||||
executer = null;
|
||||
}
|
||||
} else {
|
||||
if (executer == null) {
|
||||
SafeStatusAnnouncerTask announcer =
|
||||
new SafeStatusAnnouncerTask(this);
|
||||
executer = new PeriodicTaskExecuter(announcer, this);
|
||||
}
|
||||
executer.scheduleTask(announcementFrequency * 60000);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(getString(R.string.split_frequency_key))) {
|
||||
splitManager.setSplitFrequency(
|
||||
sharedPreferences.getInt(getString(R.string.split_frequency_key), 0));
|
||||
}
|
||||
if (key == null
|
||||
|| key.equals(getString(R.string.signal_sampling_frequency_key))) {
|
||||
signalManager.setFrequency(sharedPreferences.getInt(
|
||||
getString(R.string.signal_sampling_frequency_key), -1), this);
|
||||
}
|
||||
if (key == null || key.equals(getString(R.string.metric_units_key))) {
|
||||
splitManager.setMetricUnits(sharedPreferences.getBoolean(
|
||||
getString(R.string.metric_units_key), true));
|
||||
}
|
||||
|
||||
if (isRecording) {
|
||||
registerLocationListener();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Application lifetime events: ============================
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onCreate");
|
||||
super.onCreate();
|
||||
onCreateWasCalled = true;
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
notificationManager =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
locationManager =
|
||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
splitManager = new SplitManager(this);
|
||||
try {
|
||||
signalManager =
|
||||
new TaskExecuterManager(-1, new SignalStrengthTaskModern(this), this);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService using modern signal strength api.");
|
||||
} catch (LinkageError e) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"TrackRecordingService could not load modern signal strength.", e);
|
||||
signalManager =
|
||||
new TaskExecuterManager(-1, new SignalStrengthTask(this), this);
|
||||
}
|
||||
onSharedPreferenceChanged(null);
|
||||
restoreStats();
|
||||
registerLocationListener();
|
||||
acquireWakeLock();
|
||||
/**
|
||||
* After 5 min, check every minute that location listener still is
|
||||
* registered and spit out additional debugging info to the logs:
|
||||
*/
|
||||
timer.schedule(checkLocationListener, 1000 * 60 * 5, 1000 * 60);
|
||||
isRecording = true;
|
||||
if (mTTSAvailable && (announcementFrequency != -1)) {
|
||||
if (executer == null) {
|
||||
SafeStatusAnnouncerTask announcer = new SafeStatusAnnouncerTask(this);
|
||||
executer = new PeriodicTaskExecuter(announcer, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onDestroy");
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
isRecording = false;
|
||||
showNotification();
|
||||
unregisterLocationListener();
|
||||
if (executer != null) {
|
||||
executer.shutdown();
|
||||
}
|
||||
splitManager.shutdown();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onBind");
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onUnbind");
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopService(Intent name) {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.stopService");
|
||||
unregisterLocationListener();
|
||||
return super.stopService(name);
|
||||
}
|
||||
|
||||
public long insertWaypointMarker(Waypoint waypoint) {
|
||||
if (waypoint.getLocation() != null) {
|
||||
waypoint.setLength(length);
|
||||
waypoint.setDuration(waypoint.getLocation().getTime()
|
||||
- statsBuilder.getStatistics().getStartTime());
|
||||
Uri uri = providerUtils.insertWaypoint(waypoint);
|
||||
return Long.parseLong(uri.getLastPathSegment());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a statistics marker. A statistics marker holds the stats for the
|
||||
* last segment up to this marker.
|
||||
*
|
||||
* @param location the location where to insert
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
public long insertStatisticsMarker(Location location) {
|
||||
StringUtils utils = new StringUtils(TrackRecordingService.this);
|
||||
|
||||
// Create a new waypoint to save
|
||||
Waypoint waypoint = new Waypoint();
|
||||
|
||||
// Set stop and total time in the stats data
|
||||
final long time = System.currentTimeMillis();
|
||||
waypointStatsBuilder.pauseAt(time);
|
||||
|
||||
// Override the duration - it's not the duration from the last waypoint, but
|
||||
// the duration from the beginning of the whole track
|
||||
waypoint.setDuration(time - statsBuilder.getStatistics().getStartTime());
|
||||
|
||||
// Set the rest of the waypoint data
|
||||
waypoint.setTrackId(recordingTrackId);
|
||||
waypoint.setType(Waypoint.TYPE_STATISTICS);
|
||||
waypoint.setName(TrackRecordingService.this.getString(R.string.statistics));
|
||||
waypoint.setStatistics(waypointStatsBuilder.getStatistics());
|
||||
waypoint.setDescription(utils.generateWaypointDescription(waypoint));
|
||||
waypoint.setLocation(location);
|
||||
waypoint.setIcon(STATISTICS_ICON_URL);
|
||||
waypoint.setLength(length);
|
||||
|
||||
waypoint.setStartId(providerUtils.getLastLocationId(recordingTrackId));
|
||||
Uri uri = providerUtils.insertWaypoint(waypoint);
|
||||
|
||||
// Create a new stats keeper for the next marker
|
||||
waypointStatsBuilder = new TripStatisticsBuilder();
|
||||
waypointStatsBuilder.resumeAt(time);
|
||||
updateCurrentWaypoint();
|
||||
return Long.parseLong(uri.getLastPathSegment());
|
||||
}
|
||||
|
||||
/**
|
||||
* The ITrackRecordingService is defined through IDL.
|
||||
*/
|
||||
private final ITrackRecordingService.Stub binder =
|
||||
new ITrackRecordingService.Stub() {
|
||||
@Override
|
||||
public boolean isRecording() {
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRecordingTrackId() {
|
||||
return recordingTrackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRecorded() {
|
||||
return providerUtils.getLastTrackId() >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long startNewTrack() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.startNewTrack");
|
||||
Track track = new Track();
|
||||
TripStatistics trackStats = track.getStatistics();
|
||||
track.setName("new");
|
||||
long startTime = System.currentTimeMillis();
|
||||
trackStats.setStartTime(startTime);
|
||||
track.setStartId(-1);
|
||||
Uri trackUri = providerUtils.insertTrack(track);
|
||||
long trackId = Long.parseLong(trackUri.getLastPathSegment());
|
||||
track.setId(trackId);
|
||||
track.setName(String.format(getString(R.string.new_track), trackId));
|
||||
providerUtils.updateTrack(track);
|
||||
recordingTrackId = trackId;
|
||||
currentWaypointId = insertStatisticsMarker(null);
|
||||
isRecording = true;
|
||||
isMoving = true;
|
||||
statsBuilder = new TripStatisticsBuilder();
|
||||
statsBuilder.resumeAt(startTime);
|
||||
if (announcementFrequency != -1 && executer != null) {
|
||||
executer.scheduleTask(announcementFrequency * 60000);
|
||||
}
|
||||
length = 0;
|
||||
showNotification();
|
||||
registerLocationListener();
|
||||
splitManager.restore();
|
||||
signalManager.restore();
|
||||
return trackId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given waypoint marker. Users can insert waypoint markers
|
||||
* to tag locations with a name, description, category etc.
|
||||
*
|
||||
* @param waypoint a waypoint
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
@Override
|
||||
public long insertWaypointMarker(Waypoint waypoint) {
|
||||
return TrackRecordingService.this.insertWaypointMarker(waypoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a statistics marker. A statistics marker holds the stats for
|
||||
* the last segment up to this marker.
|
||||
*
|
||||
* @param location the location where to insert
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
@Override
|
||||
public long insertStatisticsMarker(Location location) {
|
||||
return TrackRecordingService.this.insertStatisticsMarker(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endCurrentTrack() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.endCurrentTrack");
|
||||
isRecording = false;
|
||||
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
|
||||
if (recordingTrack != null) {
|
||||
TripStatistics stats = recordingTrack.getStatistics();
|
||||
stats.setStopTime(System.currentTimeMillis());
|
||||
stats.setTotalTime(stats.getStopTime() - stats.getStartTime());
|
||||
long lastRecordedLocationId =
|
||||
providerUtils.getLastLocationId(recordingTrackId);
|
||||
ContentValues values = new ContentValues();
|
||||
if (lastRecordedLocationId >= 0
|
||||
&& recordingTrack.getStopId() >= 0) {
|
||||
values.put(TracksColumns.STOPID, lastRecordedLocationId);
|
||||
}
|
||||
values.put(TracksColumns.STOPTIME, stats.getStopTime());
|
||||
values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
|
||||
getContentResolver().update(TracksColumns.CONTENT_URI, values,
|
||||
"_id=" + recordingTrack.getId(), null);
|
||||
}
|
||||
showNotification();
|
||||
recordingTrackId = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllTracks() {
|
||||
endCurrentTrack();
|
||||
providerUtils.deleteAllTracks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordLocation(Location loc) {
|
||||
onLocationChanged(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sharedPreferenceChanged(String key) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService.sharedPrefereneChange");
|
||||
onSharedPreferenceChanged(key);
|
||||
}
|
||||
};
|
||||
|
||||
TripStatistics getTripStatistics() {
|
||||
return statsBuilder.getStatistics();
|
||||
}
|
||||
|
||||
Location getLastLocation() {
|
||||
return lastLocation;
|
||||
}
|
||||
|
||||
long getRecordingTrackId() {
|
||||
return recordingTrackId;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user