From 6bb87e1db97f91f5f6a0f22b7c57a3eae226fb84 Mon Sep 17 00:00:00 2001 From: Jimmy Shih Date: Sat, 28 Jan 2012 16:13:12 -0800 Subject: [PATCH] merge heads --- MyTracks/res/values/strings.xml | 7 - .../android/apps/mytracks/ChartActivity.java | 8 +- .../android/apps/mytracks/CheckUnits.java | 7 +- .../apps/mytracks/DeleteAllTracks.java | 4 +- .../android/apps/mytracks/DeleteTrack.java | 4 +- .../android/apps/mytracks/MapActivity.java | 8 +- .../android/apps/mytracks/MyTracks.java | 13 +- .../apps/mytracks/SettingsActivity.java | 54 ++--- .../android/apps/mytracks/StatsActivity.java | 8 +- .../mytracks/content/MyTracksProvider.java | 10 +- .../apps/mytracks/content/TrackDataHub.java | 4 +- .../io/backup/PreferenceBackupHelper.java | 4 +- .../sendtogoogle/AccountChooserActivity.java | 4 +- .../SendFusionTablesAsyncTask.java | 4 +- .../UploadServiceChooserActivity.java | 4 +- .../mytracks/services/PreferenceManager.java | 8 +- .../services/TrackRecordingService.java | 28 +-- .../sensors/BluetoothConnectionManager.java | 39 +--- .../services/sensors/ZephyrMessageParser.java | 5 +- .../sensors/ant/AntDirectSensorManager.java | 4 +- .../ant/AntSrmBridgeSensorManager.java | 4 +- .../tasks/StatusAnnouncerFactory.java | 26 +-- .../services/tasks/StatusAnnouncerTask.java | 7 - .../apps/mytracks/util/Api10Adapter.java | 44 ++++ ...ApiLevel3Adapter.java => Api7Adapter.java} | 71 +++---- ...ApiLevel8Adapter.java => Api8Adapter.java} | 19 +- ...ApiLevel9Adapter.java => Api9Adapter.java} | 19 +- .../apps/mytracks/util/ApiAdapter.java | 101 ++++++++++ .../apps/mytracks/util/ApiAdapterFactory.java | 50 +++++ .../apps/mytracks/util/ApiFeatures.java | 122 ----------- .../apps/mytracks/util/ApiLevelAdapter.java | 113 ----------- .../mytracks/util/BluetoothDeviceUtils.java | 189 +++--------------- .../android/apps/mytracks/util/EulaUtil.java | 2 +- .../services/TestRecordingService.java | 64 ++++++ .../services/TrackRecordingServiceTest.java | 21 +- .../tasks/StatusAnnouncerFactoryTest.java | 62 +----- .../mytracks/util/ApiAdapterFactoryTest.java | 24 +-- .../apps/mytracks/util/ApiFeaturesTest.java | 62 ------ 38 files changed, 444 insertions(+), 783 deletions(-) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java rename MyTracks/src/com/google/android/apps/mytracks/util/{ApiLevel3Adapter.java => Api7Adapter.java} (63%) rename MyTracks/src/com/google/android/apps/mytracks/util/{ApiLevel8Adapter.java => Api8Adapter.java} (50%) rename MyTracks/src/com/google/android/apps/mytracks/util/{ApiLevel9Adapter.java => Api9Adapter.java} (61%) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java create mode 100644 MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapterFactory.java delete mode 100644 MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java delete mode 100644 MyTracks/src/com/google/android/apps/mytracks/util/ApiLevelAdapter.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/services/TestRecordingService.java rename MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel5Adapter.java => MyTracksTest/src/com/google/android/apps/mytracks/util/ApiAdapterFactoryTest.java (51%) delete mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/util/ApiFeaturesTest.java diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index a6cb81f23..1da6821c9 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -1738,13 +1738,6 @@ limitations under the License. Screenshot: settings_recording_top.png"> Voice - - Voice announcement is not available on your Android phone. - entryValues = new ArrayList(); // The actual devices - BluetoothDeviceUtils.getInstance().populateDeviceLists(entries, entryValues); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter != null) { + BluetoothDeviceUtils.populateDeviceLists(bluetoothAdapter, entries, entryValues); + } CharSequence[] entriesArray = entries.toArray(new CharSequence[entries.size()]); CharSequence[] entryValuesArray = entryValues.toArray(new CharSequence[entryValues.size()]); @@ -685,7 +673,7 @@ public class SettingsActivity extends PreferenceActivity { SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); Editor editor = prefs.edit(); editor.putString(getString(id), metricspeed); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } /** diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java index 2c705fb29..adb62c2d1 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java @@ -23,8 +23,7 @@ import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType; import com.google.android.apps.mytracks.content.TrackDataListener; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.services.ServiceUtils; -import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -32,6 +31,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.location.Location; import android.os.Bundle; +import android.speech.tts.TextToSpeech; import android.util.DisplayMetrics; import android.util.Log; import android.view.Window; @@ -105,9 +105,7 @@ public class StatsActivity extends Activity implements TrackDataListener { utils = new StatsUtilities(this); // The volume we want to control is the Text-To-Speech volume - int volumeStream = - new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); - setVolumeControlStream(volumeStream); + setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); // We don't need a window title bar: requestWindowFeature(Window.FEATURE_NO_TITLE); diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java index bba0b5448..2f959a575 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.content; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.maps.mytracks.R; import android.content.ContentProvider; @@ -417,12 +416,9 @@ public class MyTracksProvider extends ContentProvider { } else { throw new IllegalArgumentException("Unknown URL " + url); } - - if (ApiFeatures.getInstance().canReuseSQLiteQueryBuilder()) { - Log.i(Constants.TAG, - "Build query: " + qb.buildQuery(projection, selection, selectionArgs, - null, null, sortOrder, null)); - } + Log.i(Constants.TAG, "Build query: " + + qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null)); + Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); c.setNotificationUri(getContext().getContentResolver(), url); diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java index 57b798c28..4060acff8 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java @@ -28,7 +28,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBuff import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator; import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState; import com.google.android.apps.mytracks.content.TrackDataListeners.ListenerRegistration; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.maps.mytracks.R; @@ -399,7 +399,7 @@ public class TrackDataHub { // Save the selection to memory and flush. selectedTrackId = trackId; - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges( + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges( preferences.edit().putLong(SELECTED_TRACK_KEY, trackId)); // Force it to reload data from the beginning. diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java index 4a3cc2738..1b70dc1f5 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.io.backup; import com.google.android.apps.mytracks.content.ContentTypeIds; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; @@ -106,7 +106,7 @@ class PreferenceBackupHelper { byte typeId = reader.readByte(); readAndSetPreference(name, typeId, reader, editor); } - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } /** diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java index a6e22bafe..4574dbe83 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/AccountChooserActivity.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.io.sendtogoogle; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.maps.mytracks.R; import android.accounts.Account; @@ -153,7 +153,7 @@ public class AccountChooserActivity extends Activity { Constants.SETTINGS_NAME, Context.MODE_PRIVATE); Editor editor = prefs.edit(); editor.putString(getString(R.string.preferred_account_key), account.name); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } Intent intent = new Intent(); intent.putExtra(ACCOUNT, account); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendFusionTablesAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendFusionTablesAsyncTask.java index 9d62bbb8f..5d9e6a0dd 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendFusionTablesAsyncTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendFusionTablesAsyncTask.java @@ -7,7 +7,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.stats.DoubleBuffer; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.apps.mytracks.util.SystemUtils; @@ -108,7 +108,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask context = activity.getApplicationContext(); myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context); - HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport(); + HttpTransport transport = ApiAdapterFactory.getApiAdapter().getHttpTransport(); httpRequestFactory = transport.createRequestFactory(new MethodOverride()); canRetry = true; diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java index b6f3fc8d1..da27228bf 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/UploadServiceChooserActivity.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.io.sendtogoogle; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -218,7 +218,7 @@ public class UploadServiceChooserActivity extends Activity { editor.putBoolean(getString(R.string.send_to_fusion_tables_key), sendFusionTables()); editor.putBoolean(getString(R.string.send_to_docs_key), sendDocs()); } - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } /** diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java index 501fadf39..9e9c90a35 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.services; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.maps.mytracks.R; import android.content.Context; @@ -174,19 +174,19 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener { public void setAutoResumeTrackCurrentRetry(int retryAttempts) { Editor editor = sharedPreferences.edit(); editor.putInt(autoResumeTrackCurrentRetryKey, retryAttempts); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } public void setRecordingTrack(long id) { Editor editor = sharedPreferences.edit(); editor.putLong(recordingTrackKey, id); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } public void setSelectedTrack(long id) { Editor editor = sharedPreferences.edit(); editor.putLong(selectedTrackKey, id); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } public void shutdown() { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java index ee1e021cc..8b81a44df 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -36,14 +36,12 @@ import com.google.android.apps.mytracks.services.tasks.SplitTask; import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory; import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; -import com.google.android.apps.mytracks.util.ApiFeatures; -import com.google.android.apps.mytracks.util.ApiLevelAdapter; import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.maps.mytracks.R; +import com.google.common.annotations.VisibleForTesting; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ContentValues; @@ -80,7 +78,6 @@ public class TrackRecordingService extends Service { static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3; - private NotificationManager notificationManager; private LocationManager locationManager; private WakeLock wakeLock; @@ -243,8 +240,6 @@ public class TrackRecordingService extends Service { super.onCreate(); Log.d(TAG, "TrackRecordingService.onCreate"); providerUtils = MyTracksProviderUtils.Factory.get(this); - notificationManager = - (NotificationManager) getSystemService(NOTIFICATION_SERVICE); locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); setUpTaskExecutors(); @@ -369,7 +364,6 @@ public class TrackRecordingService extends Service { // Make sure we have no indirect references to this service. locationManager = null; - notificationManager = null; providerUtils = null; binder.detachFromService(); binder = null; @@ -485,8 +479,6 @@ public class TrackRecordingService extends Service { * Shows the notification message and icon in the notification bar. */ private void showNotification() { - final ApiLevelAdapter apiLevelAdapter = - ApiFeatures.getInstance().getApiAdapter(); if (isRecording) { Notification notification = new Notification( R.drawable.arrow_320, null /* tickerText */, @@ -497,16 +489,24 @@ public class TrackRecordingService extends Service { notification.setLatestEventInfo(this, getString(R.string.my_tracks_app_name), getString(R.string.track_record_notification), contentIntent); notification.flags += Notification.FLAG_NO_CLEAR; - apiLevelAdapter.startForeground(this, notificationManager, 1, - notification); + startForegroundService(notification); } else { - apiLevelAdapter.stopForeground(this, notificationManager, 1); + stopForegroundService(); } } + @VisibleForTesting + protected void startForegroundService(Notification notification) { + startForeground(1, notification); + } + + @VisibleForTesting + protected void stopForegroundService() { + stopForeground(true); + } + private void setUpTaskExecutors() { - announcementExecutor = new PeriodicTaskExecutor( - this, new StatusAnnouncerFactory(ApiFeatures.getInstance())); + announcementExecutor = new PeriodicTaskExecutor(this, new StatusAnnouncerFactory()); splitExecutor = new PeriodicTaskExecutor(this, new SplitTask.Factory()); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java index 4a3bc512b..558bfe423 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java @@ -18,7 +18,7 @@ package com.google.android.apps.mytracks.services.sensors; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Sensor; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -31,8 +31,6 @@ import android.util.Log; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.UUID; /** @@ -44,8 +42,9 @@ import java.util.UUID; * @author Sandor Dornbush */ public class BluetoothConnectionManager { - // Unique UUID for this application - private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); + + // Unique Bluetooth UUID for My Tracks + public static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); private MessageParser parser; @@ -254,41 +253,13 @@ public class BluetoothConnectionManager { // Get a BluetoothSocket for a connection with the // given BluetoothDevice try { - tmp = getSocket(); + tmp = ApiAdapterFactory.getApiAdapter().getBluetoothSocket(device); } catch (IOException e) { Log.e(Constants.TAG, "create() failed", e); } socket = tmp; } - private BluetoothSocket getSocket() throws IOException { - if (ApiFeatures.getInstance().hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord()) { - try { - return device.createInsecureRfcommSocketToServiceRecord(SPP_UUID); - } catch (IOException e) { - Log.e(Constants.TAG, "Unable to get insecure connect.", e); - } - } else { - try { - Class c = device.getClass(); - Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class); - insecure.setAccessible(true); - return (BluetoothSocket) insecure.invoke(device, 1); - } catch (SecurityException e) { - Log.e(Constants.TAG, "Unable to get insecure connect.", e); - } catch (NoSuchMethodException e) { - Log.e(Constants.TAG, "Unable to get insecure connect.", e); - } catch (IllegalArgumentException e) { - Log.e(Constants.TAG, "Unable to get insecure connect.", e); - } catch (IllegalAccessException e) { - Log.e(Constants.TAG, "Unable to get insecure connect.", e); - } catch (InvocationTargetException e) { - Log.e(Constants.TAG, "Unable to get insecure connect.", e); - } - } - return device.createRfcommSocketToServiceRecord(SPP_UUID); - } - @Override public void run() { Log.d(Constants.TAG, "BEGIN mConnectThread"); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java index b70e0fddf..a8354ee95 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.services.sensors; import com.google.android.apps.mytracks.content.Sensor; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import java.util.Arrays; @@ -62,8 +62,7 @@ public class ZephyrMessageParser implements MessageParser { // 0x1A00316550003162 produces erroneous values for Cadence and needs // a workaround based on the stride counter. // Firmware values range from field 3 to 10 (inclusive) of the byte buffer. - byte[] hardwareFirmwareId = ApiFeatures.getInstance().getApiAdapter() - .copyByteArray(buffer, 3, 11); + byte[] hardwareFirmwareId = ApiAdapterFactory.getApiAdapter().copyByteArray(buffer, 3, 11); Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder(); if (Arrays.equals(hardwareFirmwareId, CADENCE_BUG_FW_ID)) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java index 4c0db0864..e72635f79 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java @@ -22,7 +22,7 @@ import com.dsi.ant.AntMesg; import com.dsi.ant.exception.AntInterfaceException; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Sensor; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.maps.mytracks.R; import android.content.Context; @@ -135,7 +135,7 @@ public class AntDirectSensorManager extends AntSensorManager { Constants.SETTINGS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(context.getString(R.string.ant_heart_rate_sensor_id_key), deviceNumberHRM); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } private void handleMessageResponse(byte[] rawMessage) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSrmBridgeSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSrmBridgeSensorManager.java index 1db754b4d..1006e4a6b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSrmBridgeSensorManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSrmBridgeSensorManager.java @@ -22,7 +22,7 @@ import com.dsi.ant.AntMesg; import com.dsi.ant.exception.AntInterfaceException; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Sensor; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.maps.mytracks.R; @@ -199,7 +199,7 @@ public class AntSrmBridgeSensorManager extends AntSensorManager { Constants.SETTINGS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(context.getString(R.string.ant_srm_bridge_sensor_id_key), deviceNumber); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } private void handleMessageResponse(byte[] rawMessage) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java index 739b81cb3..7e8b56b37 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java @@ -15,10 +15,9 @@ */ package com.google.android.apps.mytracks.services.tasks; -import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; import android.content.Context; -import android.media.AudioManager; /** * Factory which wraps construction and setup of text-to-speech announcements in @@ -28,30 +27,11 @@ import android.media.AudioManager; */ public class StatusAnnouncerFactory implements PeriodicTaskFactory { - private final boolean hasTts; - - public StatusAnnouncerFactory(ApiFeatures apiFeatures) { - this.hasTts = apiFeatures.hasTextToSpeech(); + public StatusAnnouncerFactory() { } @Override public PeriodicTask create(Context context) { - if (hasTts) { - return ApiFeatures.getInstance().getApiAdapter().getStatusAnnouncerTask(context); - } else { - return null; - } - } - - /** - * Returns the appropriate volume stream for controlling announcement - * volume. - */ - public int getVolumeStream() { - if (hasTts) { - return StatusAnnouncerTask.getVolumeStream(); - } else { - return AudioManager.USE_DEFAULT_STREAM_TYPE; - } + return ApiAdapterFactory.getApiAdapter().getStatusAnnouncerTask(context); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java index f6532249b..24bbd674b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java @@ -331,13 +331,6 @@ public class StatusAnnouncerTask implements PeriodicTask { } } - /** - * Returns the volume stream to use for controlling announcement volume. - */ - public static int getVolumeStream() { - return TextToSpeech.Engine.DEFAULT_STREAM; - } - /** * Gets a string to announce the time. * diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java new file mode 100644 index 000000000..8bdece372 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api10Adapter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.util; + +import com.google.android.apps.mytracks.Constants; +import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.util.Log; + +import java.io.IOException; + +/** + * API level 10 specific implementation of the {@link ApiAdapter}. + * + * @author Jimmy Shih + */ +public class Api10Adapter extends Api9Adapter { + + @Override + public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException { + try { + return bluetoothDevice.createInsecureRfcommSocketToServiceRecord( + BluetoothConnectionManager.SPP_UUID); + } catch (IOException e) { + Log.d(Constants.TAG, "Unable to create insecure connection", e); + } + return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID); + }; +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel3Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java similarity index 63% rename from MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel3Adapter.java rename to MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java index dcd033e14..6b572d045 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel3Adapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java @@ -15,50 +15,32 @@ */ package com.google.android.apps.mytracks.util; -import static com.google.android.apps.mytracks.Constants.TAG; - +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; +import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager; import com.google.android.apps.mytracks.services.tasks.PeriodicTask; import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.apache.ApacheHttpTransport; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Service; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.util.Log; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** - * API level 3 specific implementation of the {@link ApiLevelAdapter}. + * API level 7 specific implementation of the {@link ApiAdapter}. * * @author Bartlomiej Niechwiej */ -public class ApiLevel3Adapter implements ApiLevelAdapter { +public class Api7Adapter implements ApiAdapter { - @Override - public void startForeground(Service service, - NotificationManager notificationManager, int id, - Notification notification) { - setServiceForeground(service, true); - - notificationManager.notify(id, notification); - } - - @Override - public void stopForeground(Service service, - NotificationManager notificationManager, int id) { - setServiceForeground(service, false); - if (id != -1) { - notificationManager.cancel(id); - } - } - @Override public PeriodicTask getStatusAnnouncerTask(Context context) { return new StatusAnnouncerTask(context); @@ -74,24 +56,6 @@ public class ApiLevel3Adapter implements ApiLevelAdapter { }; } - private void setServiceForeground(Service service, boolean foreground) { - // setForeground has been completely removed in API level 11, so we use reflection. - try { - Method setForegroundMethod = Service.class.getMethod("setForeground", boolean.class); - setForegroundMethod.invoke(service, foreground); - } catch (SecurityException e) { - Log.e(TAG, "Unable to set service foreground state", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Unable to set service foreground state", e); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Unable to set service foreground state", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "Unable to set service foreground state", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Unable to set service foreground state", e); - } - } - @Override public void applyPreferenceChanges(Editor editor) { editor.commit(); @@ -114,4 +78,25 @@ public class ApiLevel3Adapter implements ApiLevelAdapter { public HttpTransport getHttpTransport() { return new ApacheHttpTransport(); } + + @Override + public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException { + try { + Class c = bluetoothDevice.getClass(); + Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class); + insecure.setAccessible(true); + return (BluetoothSocket) insecure.invoke(bluetoothDevice, 1); + } catch (SecurityException e) { + Log.d(Constants.TAG, "Unable to create insecure connection", e); + } catch (NoSuchMethodException e) { + Log.d(Constants.TAG, "Unable to create insecure connection", e); + } catch (IllegalArgumentException e) { + Log.d(Constants.TAG, "Unable to create insecure connection", e); + } catch (IllegalAccessException e) { + Log.d(Constants.TAG, "Unable to create insecure connection", e); + } catch (InvocationTargetException e) { + Log.d(Constants.TAG, "Unable to create insecure connection", e); + } + return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID); + } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel8Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api8Adapter.java similarity index 50% rename from MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel8Adapter.java rename to MyTracks/src/com/google/android/apps/mytracks/util/Api8Adapter.java index 35696189d..f8371e081 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel8Adapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api8Adapter.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package com.google.android.apps.mytracks.util; import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; @@ -8,11 +23,11 @@ import com.google.android.apps.mytracks.services.tasks.PeriodicTask; import android.content.Context; /** - * API level 8 specific implementation of the {@link ApiLevelAdapter}. + * API level 8 specific implementation of the {@link ApiAdapter}. * * @author Jimmy Shih */ -public class ApiLevel8Adapter extends ApiLevel5Adapter { +public class Api8Adapter extends Api7Adapter { @Override public PeriodicTask getStatusAnnouncerTask(Context context) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel9Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api9Adapter.java similarity index 61% rename from MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel9Adapter.java rename to MyTracks/src/com/google/android/apps/mytracks/util/Api9Adapter.java index 5ab6ab0aa..86fb12d3d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel9Adapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api9Adapter.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package com.google.android.apps.mytracks.util; import com.google.android.apps.mytracks.Constants; @@ -11,11 +26,11 @@ import android.util.Log; import java.util.Arrays; /** - * API level 9 specific implementation of the {@link ApiLevelAdapter}. + * API level 9 specific implementation of the {@link ApiAdapter}. * * @author Rodrigo Damazio */ -public class ApiLevel9Adapter extends ApiLevel8Adapter { +public class Api9Adapter extends Api8Adapter { @Override public void applyPreferenceChanges(Editor editor) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java new file mode 100644 index 000000000..868c43af1 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.util; + +import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; +import com.google.android.apps.mytracks.services.tasks.PeriodicTask; +import com.google.api.client.http.HttpTransport; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.content.Context; +import android.content.SharedPreferences; + +import java.io.IOException; + +/** + * A set of methods that may be implemented differently depending on the Android + * API level. + * + * @author Bartlomiej Niechwiej + */ +public interface ApiAdapter { + + /** + * Gets a status announcer task. + *

+ * Due to changes in API level 8. + * + * @param context the context + */ + public PeriodicTask getStatusAnnouncerTask(Context context); + + /** + * Gets a {@link BackupPreferencesListener}. + *

+ * Due to changes in API level 8. + * + * @param context the context + */ + public BackupPreferencesListener getBackupPreferencesListener(Context context); + + /** + * Applies all the changes done to a given preferences editor. Changes may or + * may not be applied immediately. + *

+ * Due to changes in API level 9. + * + * @param editor the editor + */ + public void applyPreferenceChanges(SharedPreferences.Editor editor); + + /** + * Enables strict mode where supported, only if this is a development build. + *

+ * Due to changes in API level 9. + */ + public void enableStrictMode(); + + /** + * Copies elements from an input byte array into a new byte array, from + * indexes start (inclusive) to end (exclusive). The end index must be less + * than or equal to the input length. + *

+ * Due to changes in API level 9. + * + * @param input the input byte array + * @param start the start index + * @param end the end index + * @return a new array containing elements from the input byte array. + */ + public byte[] copyByteArray(byte[] input, int start, int end); + + /** + * Gets a {@link HttpTransport}. + *

+ * Due to changes in API level 9. + */ + public HttpTransport getHttpTransport(); + + /** + * Gets a {@link BluetoothSocket}. + *

+ * Due to changes in API level 10. + * + * @param bluetoothDevice + */ + public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException; +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapterFactory.java new file mode 100644 index 000000000..5af16534e --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapterFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.util; + +import android.os.Build; + +/** + * A factory to get the {@link ApiAdapter} for the current device. + * + * @author Rodrigo Damazio + */ +public class ApiAdapterFactory { + + private static ApiAdapter apiAdapter; + + /** + * Gets the {@link ApiAdapter} for the current device. + */ + public static ApiAdapter getApiAdapter() { + if (apiAdapter == null) { + if (Build.VERSION.SDK_INT >= 10) { + apiAdapter = new Api10Adapter(); + return apiAdapter; + } else if (Build.VERSION.SDK_INT >= 9) { + apiAdapter = new Api9Adapter(); + return apiAdapter; + } else if (Build.VERSION.SDK_INT >= 8) { + apiAdapter = new Api8Adapter(); + return apiAdapter; + } else { + apiAdapter = new Api7Adapter(); + return apiAdapter; + } + } + return apiAdapter; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java deleted file mode 100644 index 700b5a2a6..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.android.apps.mytracks.util; - -import com.google.android.apps.mytracks.Constants; - -import android.os.Build; -import android.util.Log; - -/** - * Utility class for determining if newer-API features are available on the - * current device. - * - * @author Rodrigo Damazio - */ -public class ApiFeatures { - - /** - * The API level of the Android version we are being run under. - */ - private static final int ANDROID_API_LEVEL = Integer.parseInt( - Build.VERSION.SDK); - - private static ApiFeatures instance; - - /** - * The API level adapter for the Android version we are being run under. - */ - private ApiLevelAdapter apiLevelAdapter; - - /** - * Returns the singleton instance of this class. - */ - public static ApiFeatures getInstance() { - if (instance == null) { - instance = new ApiFeatures(); - } - return instance; - } - - /** - * Injects a specific singleton instance, to be used for unit tests. - */ - @SuppressWarnings("hiding") - public static void injectInstance(ApiFeatures instance) { - ApiFeatures.instance = instance; - } - - /** - * Allow subclasses for mocking, but no direct instantiation. - */ - protected ApiFeatures() { - // It is safe to import unsupported classes as long as we only actually - // load the class when supported. - if (getApiLevel() >= 9) { - apiLevelAdapter = new ApiLevel9Adapter(); - } else if (getApiLevel() >= 8) { - apiLevelAdapter = new ApiLevel8Adapter(); - } else if (getApiLevel() >= 5) { - apiLevelAdapter = new ApiLevel5Adapter(); - } else { - apiLevelAdapter = new ApiLevel3Adapter(); - } - - Log.i(Constants.TAG, "Using API level adapter " + apiLevelAdapter.getClass()); - } - - public ApiLevelAdapter getApiAdapter() { - return apiLevelAdapter; - } - - // API Level 4 Changes - - /** - * Returns whether text-to-speech is available. - */ - public boolean hasTextToSpeech() { - return getApiLevel() >= 4; - } - - // API Level 5 Changes - - /** - * There's a bug (#1587) in Cupcake and Donut which prevents you from - * using a SQLiteQueryBuilder twice. That is, if you call buildQuery - * on a given instance (to log the statement for debugging), and then - * call query on the same instance to make it actually do the query, - * it'll regenerate the query for the second call, and will screw it - * up. Specifically, it'll add extra parens which don't belong. - */ - public boolean canReuseSQLiteQueryBuilder() { - return getApiLevel() >= 5; - } - - // API Level 10 changes - - /** - * Returns true if BluetoothDevice.createInsecureRfcommSocketToServiceRecord - * is available. - */ - public boolean hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord() { - return getApiLevel() >= 10; - } - - // Visible for testing. - protected int getApiLevel() { - return ANDROID_API_LEVEL; - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevelAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevelAdapter.java deleted file mode 100644 index f2180dfc2..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevelAdapter.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.android.apps.mytracks.util; - -import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; -import com.google.android.apps.mytracks.services.tasks.PeriodicTask; -import com.google.api.client.http.HttpTransport; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Service; -import android.content.Context; -import android.content.SharedPreferences; - -/** - * A set of methods that may be implemented differently depending on the Android API level. - * - * @author Bartlomiej Niechwiej - */ -public interface ApiLevelAdapter { - - /** - * Puts the specified service into foreground. - * - * Due to changes in API level 5. - * - * @param service the service to be put in foreground. - * @param notificationManager the notification manager used to post the given - * notification. - * @param id the ID of the notification, unique within the application. - * @param notification the notification to post. - */ - void startForeground(Service service, NotificationManager notificationManager, - int id, Notification notification); - - /** - * Puts the given service into background. - * - * Due to changes in API level 5. - * - * @param service the service to put into background. - * @param notificationManager the notification manager to user when removing - * notifications. - * @param id the ID of the notification to be remove, or -1 if the - * notification shouldn't be removed. - */ - void stopForeground(Service service, NotificationManager notificationManager, - int id); - - /** - * Gets a status announcer task. - * - * Due to changes in API level 8. - */ - PeriodicTask getStatusAnnouncerTask(Context context); - - /** - * Gets a {@link BackupPreferencesListener}. - * - * Due to changes in API level 8. - */ - BackupPreferencesListener getBackupPreferencesListener(Context context); - - /** - * Applies all changes done to the given preferences editor. - * Changes may or may not be applied immediately. - * - * Due to changes in API level 9. - */ - void applyPreferenceChanges(SharedPreferences.Editor editor); - - /** - * Enables strict mode where supported, only if this is a development build. - * - * Due to changes in API level 9. - */ - void enableStrictMode(); - - /** - * Copies elements from the input byte array into a new byte array, from - * indexes start (inclusive) to end (exclusive). The end index must be less - * than or equal to input.length. - * - * Due to changes in API level 9. - * - * @param input the input byte array - * @param start the start index - * @param end the end index - * @return a new array containing elements from the input byte array - */ - byte[] copyByteArray(byte[] input, int start, int end); - - - /** - * Gets a {@link HttpTransport}. - * - * Due to changes in API level 9. - */ - HttpTransport getHttpTransport(); -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java index 96e8bd878..548d873c7 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java @@ -15,186 +15,49 @@ */ package com.google.android.apps.mytracks.util; -import com.google.android.apps.mytracks.Constants; - import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.os.Build; -import android.util.Log; import java.util.List; import java.util.Set; /** * Utilities for dealing with bluetooth devices. - * This can be used safely even in systems that don't support bluetooth, - * in which case a dummy implementation will be used. * * @author Rodrigo Damazio */ -public abstract class BluetoothDeviceUtils { - public static final String ANY_DEVICE = "any"; - private static BluetoothDeviceUtils instance; +public class BluetoothDeviceUtils { + private BluetoothDeviceUtils() {} + /** - * Dummy implementation, for systems that don't support bluetooth. - */ - private static class DummyImpl extends BluetoothDeviceUtils { - @Override - public void populateDeviceLists(List deviceNames, List deviceAddresses) { - // Do nothing - no devices to add - } - - @Override - public BluetoothDevice findDeviceMatching(String targetDeviceAddress) { - return null; - } - } - - /** - * Real implementation, for systems that DO support bluetooth. - */ - private static class RealImpl extends BluetoothDeviceUtils { - private final BluetoothAdapter bluetoothAdapter; - - public RealImpl() { - bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (bluetoothAdapter == null) { - throw new IllegalStateException("Unable to get bluetooth adapter"); - } - } - - @Override - public void populateDeviceLists(List deviceNames, List deviceAddresses) { - ensureNotDiscovering(); - - Set pairedDevices = bluetoothAdapter.getBondedDevices(); - for (BluetoothDevice device : pairedDevices) { - BluetoothClass bluetoothClass = device.getBluetoothClass(); - if (bluetoothClass != null) { - // Not really sure what we want, but I know what we don't want. - switch(bluetoothClass.getMajorDeviceClass()) { - case BluetoothClass.Device.Major.COMPUTER: - case BluetoothClass.Device.Major.PHONE: - break; - default: - deviceAddresses.add(device.getAddress()); - deviceNames.add(device.getName()); - } - } - } - } - - @Override - public BluetoothDevice findDeviceMatching(String targetDeviceAddress) { - if (targetDeviceAddress.equals(ANY_DEVICE)) { - return findAnyDevice(); - } else { - return findDeviceByAddress(targetDeviceAddress); - } - } - - /** - * Finds and returns the first suitable bluetooth sensor. - */ - private BluetoothDevice findAnyDevice() { - ensureNotDiscovering(); - - Set pairedDevices = bluetoothAdapter.getBondedDevices(); - for (BluetoothDevice device : pairedDevices) { - // Look for the first paired computer device - if (isSuitableDevice(device)) { - return device; - } - } - - return null; - } - - /** - * Finds and returns a device with the given address, or null if it's not - * a suitable sensor. - */ - private BluetoothDevice findDeviceByAddress(String targetDeviceAddress) { - ensureNotDiscovering(); - - BluetoothDevice device = bluetoothAdapter.getRemoteDevice(targetDeviceAddress); - if (isSuitableDevice(device)) { - return device; - } - - return null; - } - - /** - * Ensures the bluetooth adapter is not in discovery mode. - */ - private void ensureNotDiscovering() { - // If it's in discovery mode, cancel that for now. - bluetoothAdapter.cancelDiscovery(); - } - - /** - * Checks whether the given device is a suitable sensor. - * - * @param device the device to check - * @return true if it's suitable, false otherwise - */ - private boolean isSuitableDevice(BluetoothDevice device) { - // Check that the device is bonded - if (device.getBondState() != BluetoothDevice.BOND_BONDED) { - return false; - } - - return true; - } - } - - /** - * Returns the proper (singleton) instance of this class. - */ - public static BluetoothDeviceUtils getInstance() { - if (instance == null) { - if (!isBluetoothMethodSupported()) { - Log.d(Constants.TAG, "Using dummy bluetooth utils"); - instance = new DummyImpl(); - } else { - Log.d(Constants.TAG, "Using real bluetooth utils"); - try { - instance = new RealImpl(); - } catch (IllegalStateException ise) { - Log.w(Constants.TAG, "Oops, I mean, using dummy bluetooth utils", ise); - instance = new DummyImpl(); - } - } - } - return instance; - } - - /** - * Populates the given lists with the names and addresses of all suitable + * Populates the device names and the device addresses with all the suitable * bluetooth devices. * - * @param deviceNames the list to populate with user-visible names - * @param deviceAddresses the list to populate with device addresses + * @param bluetoothAdapter the bluetooth adapter + * @param deviceNames list of device names + * @param deviceAddresses list of device addresses */ - public abstract void populateDeviceLists(List deviceNames, List deviceAddresses); + public static void populateDeviceLists( + BluetoothAdapter bluetoothAdapter, List deviceNames, List deviceAddresses) { + // Ensure the bluetooth adapter is not in discovery mode. + bluetoothAdapter.cancelDiscovery(); - /** - * Finds the bluetooth device with the given address. - * - * @param targetDeviceAddress the address of the device, or - * {@link #ANY_DEVICE} for using the first suitable device - * @return the device's descriptor, or null if not found - */ - public abstract BluetoothDevice findDeviceMatching(String targetDeviceAddress); - - /** - * @return whether the bluetooth method is supported on this device - */ - public static boolean isBluetoothMethodSupported() { - return Integer.parseInt(Build.VERSION.SDK) >= 5; + Set pairedDevices = bluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : pairedDevices) { + BluetoothClass bluetoothClass = device.getBluetoothClass(); + if (bluetoothClass != null) { + // Not really sure what we want, but I know what we don't want. + switch (bluetoothClass.getMajorDeviceClass()) { + case BluetoothClass.Device.Major.COMPUTER: + case BluetoothClass.Device.Major.PHONE: + break; + default: + deviceAddresses.add(device.getAddress()); + deviceNames.add(device.getName()); + } + } + } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/EulaUtil.java b/MyTracks/src/com/google/android/apps/mytracks/util/EulaUtil.java index 0008b6e8e..6d2253844 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/EulaUtil.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/EulaUtil.java @@ -45,7 +45,7 @@ public class EulaUtil { EULA_PREFERENCE_FILE, Context.MODE_PRIVATE); Editor editor = preferences.edit(); editor.putBoolean(EULA_PREFERENCE_KEY, true); - ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); } public static String getEulaMessage(Context context) { diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/TestRecordingService.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/TestRecordingService.java new file mode 100644 index 000000000..2eaa20aad --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TestRecordingService.java @@ -0,0 +1,64 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +package com.google.android.apps.mytracks.services; + +import android.app.Notification; +import android.app.Service; +import android.test.ServiceTestCase; +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A {@link TrackRecordingService} that can be used with {@link ServiceTestCase}. + * {@link ServiceTestCase} throws a null pointer exception when the service + * calls {@link Service#startForeground(int, android.app.Notification)} and + * {@link Service#stopForeground(boolean)}. + *

+ * See http://code.google.com/p/android/issues/detail?id=12122 + *

+ * Wrap these two methods in wrappers and override them. + * + * @author Jimmy Shih + */ +public class TestRecordingService extends TrackRecordingService { + + private static final String TAG = TestRecordingService.class.getSimpleName(); + + @Override + protected void startForegroundService(Notification notification) { + try { + Method setForegroundMethod = Service.class.getMethod("setForeground", boolean.class); + setForegroundMethod.invoke(this, true); + } catch (SecurityException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (InvocationTargetException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } + } + + @Override + protected void stopForegroundService() { + try { + Method setForegroundMethod = Service.class.getMethod("setForeground", boolean.class); + setForegroundMethod.invoke(this, false); + } catch (SecurityException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } catch (InvocationTargetException e) { + Log.e(TAG, "Unable to start a service in foreground", e); + } + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java index 25fe512ef..df0b079a3 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java @@ -24,7 +24,6 @@ import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.WaypointCreationRequest; import com.google.android.apps.mytracks.stats.TripStatistics; -import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.maps.mytracks.R; import android.content.BroadcastReceiver; @@ -57,8 +56,7 @@ import java.util.List; * is called, which prevents from testing multiple scenarios in a single * test (see runFunctionTest for more details). */ -public class TrackRecordingServiceTest - extends ServiceTestCase { +public class TrackRecordingServiceTest extends ServiceTestCase { private Context context; private MyTracksProviderUtils providerUtils; @@ -74,7 +72,7 @@ public class TrackRecordingServiceTest private Intent serviceIntent; public TrackRecordingServiceTest() { - super(TrackRecordingService.class); + super(TestRecordingService.class); } /** @@ -96,19 +94,6 @@ public class TrackRecordingServiceTest } } - /** - * A mock class that forces API level < 5 to make sure we can workaround a bug - * in ServiceTestCase (throwing a NPE). - * See http://code.google.com/p/android/issues/detail?id=12122 for more - * details. - */ - private static class MockApiFeatures extends ApiFeatures { - @Override - protected int getApiLevel() { - return 4; - } - } - @Override protected IBinder bindService(Intent intent) { if (getService() != null) { @@ -138,8 +123,6 @@ public class TrackRecordingServiceTest protected void setUp() throws Exception { super.setUp(); - ApiFeatures.injectInstance(new MockApiFeatures()); - MockContentResolver mockContentResolver = new MockContentResolver(); RenamingDelegatingContext targetContext = new RenamingDelegatingContext( getContext(), getContext(), "test."); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactoryTest.java index ea9a8d441..91d9b3eeb 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactoryTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactoryTest.java @@ -15,13 +15,6 @@ */ package com.google.android.apps.mytracks.services.tasks; -import com.google.android.apps.mytracks.services.tasks.PeriodicTask; -import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory; -import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask; -import com.google.android.apps.mytracks.util.ApiFeatures; - -import android.media.AudioManager; -import android.speech.tts.TextToSpeech; import android.test.AndroidTestCase; /** @@ -31,62 +24,9 @@ import android.test.AndroidTestCase; * @author Rodrigo Damazio */ public class StatusAnnouncerFactoryTest extends AndroidTestCase { - /** - * Mock version of the {@link ApiFeatures} class. - */ - private class MockApiFeatures extends ApiFeatures { - private boolean hasTts; - - public void setHasTextToSpeech(boolean hasTts) { - this.hasTts = hasTts; - } - - @Override - public boolean hasTextToSpeech() { - return hasTts; - } - } - - private MockApiFeatures apiFeatures; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - apiFeatures = new MockApiFeatures(); - } - public void testCreate() { - apiFeatures.setHasTextToSpeech(true); - PeriodicTaskFactory factory = - new StatusAnnouncerFactory(apiFeatures); + PeriodicTaskFactory factory = new StatusAnnouncerFactory(); PeriodicTask task = factory.create(getContext()); assertTrue(task instanceof StatusAnnouncerTask); } - - public void testCreate_notAvailable() { - apiFeatures.setHasTextToSpeech(false); - PeriodicTaskFactory factory = - new StatusAnnouncerFactory(apiFeatures); - PeriodicTask task = factory.create(getContext()); - assertNull(task); - } - - public void testGetVolumeStream() { - apiFeatures.setHasTextToSpeech(true); - StatusAnnouncerFactory factory = - new StatusAnnouncerFactory(apiFeatures); - assertEquals( - TextToSpeech.Engine.DEFAULT_STREAM, - factory.getVolumeStream()); - } - - public void testGetVolumeStream_notAvailable() { - apiFeatures.setHasTextToSpeech(false); - StatusAnnouncerFactory factory = - new StatusAnnouncerFactory(apiFeatures); - assertEquals( - AudioManager.USE_DEFAULT_STREAM_TYPE, - factory.getVolumeStream()); - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel5Adapter.java b/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiAdapterFactoryTest.java similarity index 51% rename from MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel5Adapter.java rename to MyTracksTest/src/com/google/android/apps/mytracks/util/ApiAdapterFactoryTest.java index 0f963f70f..4b73a6a6f 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel5Adapter.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiAdapterFactoryTest.java @@ -15,27 +15,15 @@ */ package com.google.android.apps.mytracks.util; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Service; +import junit.framework.TestCase; /** - * API level 5 specific implementation of the {@link ApiLevelAdapter}. + * Tests {@link ApiAdapterFactory}. * - * @author Bartlomiej Niechwiej + * @author Rodrigo Damazio */ -public class ApiLevel5Adapter extends ApiLevel3Adapter { - - @Override - public void startForeground(Service service, - NotificationManager notificationManager, int id, - Notification notification) { - service.startForeground(id, notification); - } - - @Override - public void stopForeground(Service service, - NotificationManager notificationManager, int id) { - service.stopForeground(id != -1); +public class ApiAdapterFactoryTest extends TestCase { + public void testGetApiLevelAdapter() { + assertNotNull(ApiAdapterFactory.getApiAdapter()); } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiFeaturesTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiFeaturesTest.java deleted file mode 100644 index 5b08a975f..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiFeaturesTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.android.apps.mytracks.util; - -import junit.framework.TestCase; - -/** - * Tests the API feature detection code in {@link ApiFeatures}. - * This test requires Froyo+ to run. - * - * @author Rodrigo Damazio - */ -public class ApiFeaturesTest extends TestCase { - - private TestableApiFeatures features; - - private class TestableApiFeatures extends ApiFeatures { - private int apiLevel; - - public void setApiLevel(int apiLevel) { - this.apiLevel = apiLevel; - } - - @Override - protected int getApiLevel() { - return apiLevel; - } - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - - features = new TestableApiFeatures(); - } - - public void testHasTextToSpeech() { - features.setApiLevel(3); - assertFalse(features.hasTextToSpeech()); - for (int i = 4; i <= 8; i++) { - features.setApiLevel(i); - assertTrue(features.hasTextToSpeech()); - } - } - - public void testGetApiAdapter() { - assertNotNull(features.getApiAdapter()); - } -}