forked from upstream-mirrors/OpenTracks
Introduce an interface to decouple platform specific API from the classes that require it. Also:
1) Put the service into foreground to prevent from being killed by the system 2) Fix a bug the prevented MyTracks from running on pre-Eclair devices
This commit is contained in:
@@ -379,7 +379,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
|
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
|
||||||
if (updateTrackThread != null) {
|
if (updateTrackThread != null) {
|
||||||
updateTrackThread.quit();
|
ApiFeatures.getInstance().getApiPlatformAdapter().stopHandlerThread(
|
||||||
|
updateTrackThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.google.android.apps.mytracks.content.WaypointsColumns;
|
|||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiPlatformAdapter;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
@@ -329,6 +330,8 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
* Shows the notification message and icon in the notification bar.
|
* Shows the notification message and icon in the notification bar.
|
||||||
*/
|
*/
|
||||||
public void showNotification() {
|
public void showNotification() {
|
||||||
|
final ApiPlatformAdapter apiPlatformAdapter =
|
||||||
|
ApiFeatures.getInstance().getApiPlatformAdapter();
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
Notification notification = new Notification(
|
Notification notification = new Notification(
|
||||||
R.drawable.arrow_320, null /* tickerText */,
|
R.drawable.arrow_320, null /* tickerText */,
|
||||||
@@ -339,9 +342,10 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
notification.setLatestEventInfo(this, getString(R.string.app_name),
|
notification.setLatestEventInfo(this, getString(R.string.app_name),
|
||||||
getString(R.string.recording_your_track), contentIntent);
|
getString(R.string.recording_your_track), contentIntent);
|
||||||
notification.flags += Notification.FLAG_NO_CLEAR;
|
notification.flags += Notification.FLAG_NO_CLEAR;
|
||||||
notificationManager.notify(1, notification);
|
apiPlatformAdapter.startForeground(this, notificationManager, 1,
|
||||||
|
notification);
|
||||||
} else {
|
} else {
|
||||||
notificationManager.cancelAll();
|
apiPlatformAdapter.stopForeground(this, notificationManager, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.util;
|
package com.google.android.apps.mytracks.util;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for determining if newer-API features are available on the
|
* Utility class for determining if newer-API features are available on the
|
||||||
@@ -28,10 +31,16 @@ public class ApiFeatures {
|
|||||||
/**
|
/**
|
||||||
* The API level of the Android version we're being run under.
|
* The API level of the Android version we're being run under.
|
||||||
*/
|
*/
|
||||||
public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK);
|
public static final int ANDROID_API_LEVEL = Integer.parseInt(
|
||||||
|
Build.VERSION.SDK);
|
||||||
|
|
||||||
private static ApiFeatures instance;
|
private static ApiFeatures instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The API platform adapter supported by this system.
|
||||||
|
*/
|
||||||
|
private ApiPlatformAdapter apiPlatformAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the singleton instance of this class.
|
* Returns the singleton instance of this class.
|
||||||
*/
|
*/
|
||||||
@@ -52,7 +61,28 @@ public class ApiFeatures {
|
|||||||
/**
|
/**
|
||||||
* Allow subclasses for mocking, but no direct instantiation.
|
* Allow subclasses for mocking, but no direct instantiation.
|
||||||
*/
|
*/
|
||||||
protected ApiFeatures() {}
|
protected ApiFeatures() {
|
||||||
|
if (getApiLevel() >= 5) {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = Class.forName(
|
||||||
|
"com.google.android.apps.mytracks.util.EclairPlatformAdapter");
|
||||||
|
apiPlatformAdapter = (ApiPlatformAdapter) clazz.newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.i(MyTracksConstants.TAG, "ApiFeatures: Unable to instantiate Eclair"
|
||||||
|
+ " platform adapter", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (apiPlatformAdapter == null) {
|
||||||
|
Log.i(MyTracksConstants.TAG,
|
||||||
|
"ApiFeatures: Using default platform adapter");
|
||||||
|
// Cupcake adapter is always supported, so it's safe to do static linkage.
|
||||||
|
apiPlatformAdapter = new CupcakePlatformAdapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiPlatformAdapter getApiPlatformAdapter() {
|
||||||
|
return apiPlatformAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
|
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of methods that may be implemented in a platform specific way.
|
||||||
|
*
|
||||||
|
* @author Bartlomiej Niechwiej
|
||||||
|
*/
|
||||||
|
public interface ApiPlatformAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the specified service into foreground.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates the given handler thread.
|
||||||
|
*
|
||||||
|
* @param handlerThread the thread to be terminated.
|
||||||
|
* @return true whether the handler has been stopped or not.
|
||||||
|
*/
|
||||||
|
boolean stopHandlerThread(HandlerThread handlerThread);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Cupcake (API level 3) specific implementation of the
|
||||||
|
* {@link ApiPlatformAdapter}.
|
||||||
|
*
|
||||||
|
* @author Bartlomiej Niechwiej
|
||||||
|
*/
|
||||||
|
public class CupcakePlatformAdapter implements ApiPlatformAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startForeground(Service service,
|
||||||
|
NotificationManager notificationManager, int id,
|
||||||
|
Notification notification) {
|
||||||
|
service.setForeground(true);
|
||||||
|
notificationManager.notify(id, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopForeground(Service service,
|
||||||
|
NotificationManager notificationManager, int id) {
|
||||||
|
service.setForeground(false);
|
||||||
|
if (id != -1) {
|
||||||
|
notificationManager.cancel(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
||||||
|
// Do nothing, as Cupcake doesn't provide quit().
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Eclair (API level 5) specific implementation of the
|
||||||
|
* {@link ApiPlatformAdapter}.
|
||||||
|
*
|
||||||
|
* @author Bartlomiej Niechwiej
|
||||||
|
*/
|
||||||
|
public class EclairPlatformAdapter implements ApiPlatformAdapter {
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
||||||
|
return handlerThread.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@@ -57,6 +58,9 @@ public class TrackRecordingServiceTest
|
|||||||
super(TrackRecordingService.class);
|
super(TrackRecordingService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context wrapper with the user provided {@link ContentResolver}.
|
||||||
|
*/
|
||||||
private static class MockContext extends ContextWrapper {
|
private static class MockContext extends ContextWrapper {
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
|
|
||||||
@@ -71,10 +75,25 @@ 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
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
|
ApiFeatures.injectInstance(new MockApiFeatures());
|
||||||
|
|
||||||
MockContentResolver mockContentResolver = new MockContentResolver();
|
MockContentResolver mockContentResolver = new MockContentResolver();
|
||||||
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
||||||
getContext(), getContext(), "test.");
|
getContext(), getContext(), "test.");
|
||||||
|
|||||||
@@ -75,4 +75,8 @@ public class ApiFeaturesTest extends TestCase {
|
|||||||
features.setApiLevel(8);
|
features.setApiLevel(8);
|
||||||
assertTrue(features.hasModernSignalStrength());
|
assertTrue(features.hasModernSignalStrength());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetApiPlatformAdapter() {
|
||||||
|
assertNotNull(features.getApiPlatformAdapter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user