diff --git a/.gitignore b/.gitignore index c5dbf1db0..89e23f464 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ reference *.releaseBackup release.properties release -assets tokens.xml local.properties @@ -29,7 +28,17 @@ local.properties bin/ gen/ -# Gradle files +# Android Studio / Gradle files +.idea/ +.gradle +/*/local.properties +/*/out +/*/*/production +*.iml +*.iws +*.ipr +*~ +*.swp .gradle/ build/ /*/build/ diff --git a/.travis.yml b/.travis.yml index 2c3c31cc5..9e2df93af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ android: components: - platform-tools - tools - - build-tools-22.0.1 - - android-22 + - build-tools-27.0.3 + - android-27 - extra-android-support - extra-android-m2repository script: diff --git a/CHANGELOG.mkd b/CHANGELOG.mkd index 0f6e3ae39..d0e0e242d 100644 --- a/CHANGELOG.mkd +++ b/CHANGELOG.mkd @@ -1,11 +1,20 @@ # OpenXC Android Library Changelog -## v6.1.7-dev - +## v7.0.0 + +* New Feature: Now supports sending control commands on-demand +* Improvement: Added new command types to CommandType enum +* New Feature: Enabler app to include Dweet.io integration +* New Feature: Now can connect to BLE VIs +* New Feature: Bluetooth Prompt in case bluetooth is switched off and runtime location permission. +* Building with Android API level 27 (8.1) +* Updated dependencies for library and enabler. * Improvement: App will now pull and display the VI platform. * Fix: Evented messages will now show on the dashboard in enabler. * Fix: mKey is stripped from the message before it is written to the tracefile (#253). * Fix: Allow 2-byte PIDs in diagnostic request screen. +* New Feature: Now supports BugLabs Dweet.io formats +* New Feature: Now can include phone sensor data into OpenXC stream. ## v6.1.6 diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ce08c5e17..159e4558b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -9,3 +9,4 @@ Zac Nelson, znelson1@ford.com Robert Rembold, Robert.Rembold@de.bertrandt.com Colin Mayer, colinm1@ford.com Nick Thorne, nthorne5@ford.com +Alok Kumar, akuma128@ford.com diff --git a/README.mkd b/README.mkd index e7613b776..fc500cff4 100644 --- a/README.mkd +++ b/README.mkd @@ -14,7 +14,7 @@ application. Visit the [OpenXC](http://openxcplatform.com) project page for [installation instructions](http://openxcplatform.com/getting-started/library-installation.html), -[usage details](http://openxcplatform.com/android/api-guide.html) and the +[usage details](http://openxcplatform.com/android/api-guide.html), and the [source code documentation](http://android.openxcplatform.com). ## Building from Android Studio @@ -81,10 +81,10 @@ Please see our [Contribution Documents](https://github.com/openxc/openxc-android ## License -Copyright (c) 2011-2013 Ford Motor Company +Copyright (c) 2011-2017 Ford Motor Company Licensed under the BSD license. -[binding]: http://developer.android.com/guide/topics/fundamentals/bound-services.html#Binding) +[binding]: http://developer.android.com/guide/topics/fundamentals/bound-services.html#Binding [services]: http://developer.android.com/guide/topics/fundamentals/services.html [AIDL]: http://developer.android.com/guide/developing/tools/aidl.html [OpenXC]: http://openxcplatform.com diff --git a/build.gradle b/build.gradle index f29126834..c1613431d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,34 +2,37 @@ buildscript { repositories { + mavenCentral() jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.android.tools.build:gradle:3.1.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' - classpath 'com.github.triplet.gradle:play-publisher:1.1.2' - classpath 'com.bugsnag:bugsnag-android-gradle-plugin:2.0.2' + classpath 'com.github.triplet.gradle:play-publisher:1.2.0' + classpath 'com.bugsnag:bugsnag-android-gradle-plugin:3.2.5' } } allprojects { repositories { jcenter() + google() } } ext { - compileSdkVersion = 22 - buildToolsVersion = "22.0.1" - minSdkVersion = 10 - targetSdkVersion = 22 - - versionMajor = 6 - versionMinor = 1 - versionPatch = 7 + compileSdkVersion = 27 + buildToolsVersion = "27.0.3" + minSdkVersion = 18 + targetSdkVersion = 27 + + versionMajor = 7 + versionMinor = 0 + versionPatch = 0 versionCode = versionMajor * 1000000 + versionMinor * 1000 + versionPatch versionName = "${versionMajor}.${versionMinor}.${versionPatch}" diff --git a/enabler/build.gradle b/enabler/build.gradle index fe6feed77..d2dbeac3d 100644 --- a/enabler/build.gradle +++ b/enabler/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'com.android.application' -apply plugin: 'bugsnag' apply plugin: 'com.github.triplet.play' - +apply plugin: 'com.bugsnag.android.gradle' android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion @@ -12,6 +11,7 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName + multiDexEnabled true } signingConfigs { @@ -47,10 +47,10 @@ android { } applicationVariants.all { variant -> - variant.outputs.each { output -> - output.outputFile = new File( - output.outputFile.parent, - output.outputFile.name.replace(".apk", "-${variant.versionName}.apk")) + variant.outputs.all { output -> + def newName = outputFileName + newName.replace(".apk", "-${variant.versionName}.apk") + outputFileName = new File(newName) } } } @@ -76,14 +76,13 @@ android { } dependencies { - compile project(":library") - - compile 'com.android.support:support-v4:19.1.0' - compile 'com.bugsnag:bugsnag-android:3.2.2' + implementation project(":library") + implementation 'com.android.support:support-v4:27.1.1' + implementation 'com.bugsnag:bugsnag-android:4.3.2' - androidTestCompile 'junit:junit:4.12' - androidTestCompile 'org.hamcrest:hamcrest-library:1.3' - androidTestCompile 'org.mockito:mockito-core:1.10.19' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' + androidTestImplementation 'org.mockito:mockito-core:2.17.0' + androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' + androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' } diff --git a/enabler/enabler.iml b/enabler/enabler.iml index a2c07ba22..f8059485b 100644 --- a/enabler/enabler.iml +++ b/enabler/enabler.iml @@ -9,13 +9,11 @@ - + - + @@ -34,73 +32,106 @@ - + - + - + + + + + + + + - + - + + + + + + + + + - - - + - - - - + - - - + - - - - - - - + - - + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/enabler/src/main/AndroidManifest.xml b/enabler/src/main/AndroidManifest.xml index 99663cf15..d13947023 100644 --- a/enabler/src/main/AndroidManifest.xml +++ b/enabler/src/main/AndroidManifest.xml @@ -2,9 +2,7 @@ - - @@ -13,7 +11,8 @@ - + diff --git a/enabler/src/main/ic_launcher-web.png b/enabler/src/main/ic_launcher-web.png new file mode 100644 index 000000000..b8d2bbc03 Binary files /dev/null and b/enabler/src/main/ic_launcher-web.png differ diff --git a/enabler/src/main/java/com/openxc/enabler/OpenXcEnablerActivity.java b/enabler/src/main/java/com/openxc/enabler/OpenXcEnablerActivity.java index 91cc83b18..45e738340 100644 --- a/enabler/src/main/java/com/openxc/enabler/OpenXcEnablerActivity.java +++ b/enabler/src/main/java/com/openxc/enabler/OpenXcEnablerActivity.java @@ -17,9 +17,9 @@ import android.view.MenuItem; import com.bugsnag.android.Bugsnag; -import com.openxcplatform.enabler.BuildConfig; import com.openxc.VehicleManager; import com.openxc.enabler.preferences.PreferenceManagerService; +import com.openxcplatform.enabler.BuildConfig; import com.openxcplatform.enabler.R; /** The OpenXC Enabler app is primarily for convenience, but it also increases @@ -102,7 +102,7 @@ public boolean onCreateOptionsMenu(Menu menu) { public static class EnablerFragmentAdapter extends FragmentPagerAdapter { private static final String[] mTitles = { "Status", "Dashboard", - "CAN", "Diagnostic", "Send CAN" }; + "CAN", "Diagnostic", "Send CAN", "Send Command"}; public EnablerFragmentAdapter(FragmentManager fm) { super(fm); @@ -128,6 +128,8 @@ public Fragment getItem(int position) { return new DiagnosticRequestFragment(); } else if(position == 4) { return new SendCanMessageFragment(); + } else if (position == 5) { + return new SendCommandMessageFragment(); } // For position 0 or anything unrecognized, go to Status diff --git a/enabler/src/main/java/com/openxc/enabler/SendCommandMessageFragment.java b/enabler/src/main/java/com/openxc/enabler/SendCommandMessageFragment.java new file mode 100644 index 000000000..b83f914f4 --- /dev/null +++ b/enabler/src/main/java/com/openxc/enabler/SendCommandMessageFragment.java @@ -0,0 +1,401 @@ +package com.openxc.enabler; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import com.openxc.VehicleManager; +import com.openxc.interfaces.VehicleInterfaceDescriptor; +import com.openxc.messages.Command; +import com.openxc.messages.CustomCommand; +import com.openxc.messages.KeyedMessage; +import com.openxc.messages.VehicleMessage; +import com.openxc.messages.formatters.JsonFormatter; +import com.openxc.remote.VehicleServiceException; +import com.openxc.remote.ViConnectionListener; +import com.openxcplatform.enabler.R; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; + +import static android.view.View.GONE; + +public class SendCommandMessageFragment extends Fragment { + private static String TAG = "SendCommandMsgFragment"; + + public static final int SELECT_COMMAND = 0; + public static final int VERSION_POS = 1; + public static final int DEVICE_ID_POS = 2; + public static final int PLATFORM_POS = 3; + public static final int PASSTHROUGH_CAN_POS = 4; + public static final int ACCEPTANCE_BYPASS_POS = 5; + public static final int PAYLOAD_FORMAT_POS = 6; + public static final int C5_RTC_CONFIG_POS = 7; + public static final int C5_SD_CARD_POS = 8; + public static final int CUSTOM_COMMAND_POS = 9; + + private TextView commandResponseTextView; + private TextView commandRequestTextView; + private View mServiceNotRunningWarningView; + + private VehicleManager mVehicleManager; + + private LinearLayout mBusLayout; + private LinearLayout mEnabledLayout; + private LinearLayout mBypassLayout; + private LinearLayout mFormatLayout; + private LinearLayout mCustomInputLayout; + + private Spinner mBusSpinner; + private Spinner mEnabledSpinner; + private Spinner mBypassSpinner; + private Spinner mFormatSpinner; + private Button mSendButton; + private EditText mCustomInput; + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, + IBinder service) { + Log.i(TAG, "Bound to VehicleManager"); + mVehicleManager = ((VehicleManager.VehicleBinder) service + ).getService(); + + try { + mVehicleManager.addOnVehicleInterfaceConnectedListener( + mConnectionListener); + } catch (VehicleServiceException e) { + Log.e(TAG, "Unable to register VI connection listener", e); + } + + if (getActivity() == null) { + Log.w(TAG, "Status fragment detached from activity"); + } + + new Thread(new Runnable() { + public void run() { + try { + // It's possible that between starting the thread and + // this running, the manager has gone away. + if (mVehicleManager != null) { + mVehicleManager.waitUntilBound(); + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { + public void run() { + mServiceNotRunningWarningView.setVisibility(GONE); + } + }); + } + } + } catch (VehicleServiceException e) { + Log.w(TAG, "Unable to connect to VehicleService"); + } + + } + }).start(); + + } + + public synchronized void onServiceDisconnected(ComponentName className) { + Log.w(TAG, "VehicleService disconnected unexpectedly"); + mVehicleManager = null; + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { + public void run() { + mServiceNotRunningWarningView.setVisibility(View.VISIBLE); + } + }); + } + } + }; + + private ViConnectionListener mConnectionListener = new ViConnectionListener.Stub() { + public void onConnected(final VehicleInterfaceDescriptor descriptor) { + Log.d(TAG, descriptor + " is now connected"); + } + + public void onDisconnected() { + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { + public void run() { + Log.d(TAG, "VI disconnected"); + } + }); + } + } + }; + + @Override + public void onResume() { + super.onResume(); + if (getActivity() != null) { + getActivity().bindService( + new Intent(getActivity(), VehicleManager.class), + mConnection, Context.BIND_AUTO_CREATE); + } + } + + @Override + public synchronized void onPause() { + super.onPause(); + if (mVehicleManager != null) { + getActivity().unbindService(mConnection); + mVehicleManager = null; + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.send_command_message_fragment, container, false); + + commandResponseTextView = (TextView) v.findViewById(R.id.command_response); + commandRequestTextView = (TextView) v.findViewById(R.id.last_request); + mServiceNotRunningWarningView = v.findViewById(R.id.service_not_running_bar); + mBusLayout = (LinearLayout) v.findViewById(R.id.bus_layout); + mEnabledLayout = (LinearLayout) v.findViewById(R.id.enabled_layout); + mBypassLayout = (LinearLayout) v.findViewById(R.id.bypass_layout); + mFormatLayout = (LinearLayout) v.findViewById(R.id.format_layout); + mCustomInputLayout = (LinearLayout) v.findViewById(R.id.custom_input_layout); + mCustomInput = (EditText) v.findViewById(R.id.customInput); + mBusSpinner = (Spinner) v.findViewById(R.id.bus_spinner); + ArrayAdapter busAdapter = ArrayAdapter.createFromResource( + getActivity(), R.array.buses_array + , android.R.layout.simple_spinner_item); + + busAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mBusSpinner.setAdapter(busAdapter); + + mEnabledSpinner = (Spinner) v.findViewById(R.id.enabled_spinner); + ArrayAdapter enabledAdapter = ArrayAdapter.createFromResource( + getActivity(), R.array.boolean_array + , android.R.layout.simple_spinner_item); + + enabledAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mEnabledSpinner.setAdapter(enabledAdapter); + + mBypassSpinner = (Spinner) v.findViewById(R.id.bypass_spinner); + ArrayAdapter bypassAdapter = ArrayAdapter.createFromResource( + getActivity(), R.array.boolean_array + , android.R.layout.simple_spinner_item); + + bypassAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mBypassSpinner.setAdapter(bypassAdapter); + + mFormatSpinner = (Spinner) v.findViewById(R.id.format_spinner); + ArrayAdapter formatAdapter = ArrayAdapter.createFromResource( + getActivity(), R.array.format_array + , android.R.layout.simple_spinner_item); + + formatAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mFormatSpinner.setAdapter(formatAdapter); + + final Spinner commandSpinner = (Spinner) v.findViewById(R.id.command_spinner); + //set default selection as Select Command + commandSpinner.setSelection(0); + + final ArrayAdapter commandAdapter = ArrayAdapter.createFromResource( + getActivity(), R.array.commands_array + , android.R.layout.simple_spinner_item); + + commandAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + commandSpinner.setAdapter(commandAdapter); + + + commandSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + showSelectedCommandView(i); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + + }); + + mSendButton = (Button) v.findViewById(R.id.send_request); + mSendButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int selectedItem = commandSpinner.getSelectedItemPosition(); + sendRequest(selectedItem); + } + }); + + mCustomInput.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + if (hasFocus) { + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); + } else { + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + }); + + getActivity().runOnUiThread(new Runnable() { + public void run() { + mServiceNotRunningWarningView.setVisibility(View.VISIBLE); + } + }); + + return v; + } + + private void sendRequest(int selectedItem) { + if (mVehicleManager != null) { + KeyedMessage request = null; + VehicleMessage response; + int selectedBus; + Boolean enabled, bypass; + String format; + switch (selectedItem) { + case VERSION_POS: + request = new Command(Command.CommandType.VERSION); + break; + + case DEVICE_ID_POS: + request = new Command(Command.CommandType.DEVICE_ID); + break; + + case PLATFORM_POS: + request = new Command(Command.CommandType.PLATFORM); + break; + + case PASSTHROUGH_CAN_POS: + selectedBus = Integer.valueOf(mBusSpinner.getSelectedItem().toString()); + enabled = Boolean.valueOf( + mEnabledSpinner.getSelectedItem().toString()); + request = new Command( + Command.CommandType.PASSTHROUGH, selectedBus, enabled); + break; + + case ACCEPTANCE_BYPASS_POS: + selectedBus = Integer.valueOf(mBusSpinner.getSelectedItem().toString()); + bypass = Boolean.valueOf( + mBypassSpinner.getSelectedItem().toString()); + request = new Command(Command.CommandType.AF_BYPASS, bypass, selectedBus); + break; + + case PAYLOAD_FORMAT_POS: + format = mFormatSpinner.getSelectedItem().toString(); + request = new Command(format, Command.CommandType.PAYLOAD_FORMAT); + break; + + case C5_RTC_CONFIG_POS: + request = new Command(Command.CommandType.RTC_CONFIGURATION, new Date().getTime()/1000L); + break; + + case C5_SD_CARD_POS: + request = new Command(Command.CommandType.SD_MOUNT_STATUS); + break; + + case CUSTOM_COMMAND_POS: + String inputString = mCustomInput.getText().toString(); + HashMap inputCommand = getMapFromJson(inputString); + if (inputCommand == null) + mCustomInput.setError(getResources().getString(R.string.input_json_error)); + else + request = new CustomCommand(inputCommand); + break; + default: + break; + } + if (request != null) { + response = mVehicleManager.request(request); + //Update the request TextView + commandRequestTextView.setVisibility(View.VISIBLE); + commandRequestTextView.setText(JsonFormatter.serialize(request)); + + //Update the response TextView + commandResponseTextView.setVisibility(View.VISIBLE); + commandResponseTextView.setText(JsonFormatter.serialize(response)); + } + } + } + + /**** + * This method will return a map of the key value pairs received from JSON. + * If JSON is invalid it will return null. + * @param customJson + * @return HashMap + */ + private HashMap getMapFromJson(String customJson) { + HashMap command = new HashMap<>(); + if (customJson != null) { + try { + JSONObject jsonObject = new JSONObject(customJson); + Iterator iterator = jsonObject.keys(); + while (iterator.hasNext()) { + String key = (String) iterator.next(); + String value = jsonObject.getString(key); + command.put(key, value); + } + return command; + } catch (JSONException exception) { + return null; + } + } else + return null; + } + + private void showSelectedCommandView(int pos) { + commandRequestTextView.setVisibility(GONE); + commandResponseTextView.setVisibility(GONE); + mBusLayout.setVisibility(GONE); + mEnabledLayout.setVisibility(GONE); + mBypassLayout.setVisibility(GONE); + mFormatLayout.setVisibility(GONE); + mCustomInputLayout.setVisibility(GONE); + /*Send button is visible in all views*/ + mSendButton.setVisibility(View.VISIBLE); + switch (pos) { + case SELECT_COMMAND: + mSendButton.setVisibility(GONE); + break; + case PASSTHROUGH_CAN_POS: + mBusLayout.setVisibility(View.VISIBLE); + mEnabledLayout.setVisibility(View.VISIBLE); + break; + case ACCEPTANCE_BYPASS_POS: + mBusLayout.setVisibility(View.VISIBLE); + mBypassLayout.setVisibility(View.VISIBLE); + break; + case PAYLOAD_FORMAT_POS: + mFormatLayout.setVisibility(View.VISIBLE); + break; + case CUSTOM_COMMAND_POS: + mCustomInputLayout.setVisibility(View.VISIBLE); + break; + default: // do nothing + break; + } + } +} diff --git a/enabler/src/main/java/com/openxc/enabler/SettingsActivity.java b/enabler/src/main/java/com/openxc/enabler/SettingsActivity.java index f9ddc3147..b83d6a220 100644 --- a/enabler/src/main/java/com/openxc/enabler/SettingsActivity.java +++ b/enabler/src/main/java/com/openxc/enabler/SettingsActivity.java @@ -1,10 +1,5 @@ package com.openxc.enabler; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; @@ -39,6 +34,11 @@ import com.openxc.sinks.UploaderSink; import com.openxcplatform.enabler.R; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + /** * Initialize and display all preferences for the OpenXC Enabler application. * @@ -62,12 +62,16 @@ public class SettingsActivity extends PreferenceActivity { private ListPreference mVehicleInterfaceListPreference; private ListPreference mBluetoothDeviceListPreference; private CheckBoxPreference mUploadingPreference; + private CheckBoxPreference mDweetingPreference; private Preference mTraceFilePreference; private EditTextPreference mNetworkHostPreference; private EditTextPreference mNetworkPortPreference; private Preference mAboutVersionPreference; private PreferenceManagerService mPreferenceManager; + private CheckBoxPreference mPhoneSensorPreference; + + private PreferenceCategory mBluetoothPreferences; private PreferenceCategory mNetworkPreferences; private PreferenceCategory mTracePreferences; @@ -101,6 +105,7 @@ private void initializeLegacyLayout() { if(action.equals(RECORDING_PREFERENCE)) { addPreferencesFromResource(R.xml.recording_preferences); initializeUploadingPreferences(getPreferenceManager()); + initializeDweetingPreferences(getPreferenceManager()); } else if(action.equals(DATA_SOURCE_PREFERENCE)) { addPreferencesFromResource(R.xml.data_source_preferences); initializeDataSourcePreferences(getPreferenceManager()); @@ -223,6 +228,8 @@ public void onCreate(Bundle savedInstanceState) { addPreferencesFromResource(R.xml.recording_preferences); ((SettingsActivity)getActivity()).initializeUploadingPreferences( getPreferenceManager()); + ((SettingsActivity)getActivity()).initializeDweetingPreferences( + getPreferenceManager()); } } @@ -266,9 +273,15 @@ protected void initializeTracePreferences(PreferenceManager manager) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - updateSummary(mTraceFilePreference, - preferences.getString( - getString(R.string.trace_source_file_key), null)); +// updateSummary(mTraceFilePreference, +// preferences.getString( +// getString(R.string.trace_source_file_key), null)); + } + + protected void initializePhoneSensorPreferences(PreferenceManager manager) { + mPhoneSensorPreference = (CheckBoxPreference) manager.findPreference( + getString(R.string.phone_source_polling_checkbox_key)); + } protected void initializeUploadingPreferences(PreferenceManager manager) { @@ -286,6 +299,21 @@ protected void initializeUploadingPreferences(PreferenceManager manager) { getString(R.string.uploading_path_key), null)); } + protected void initializeDweetingPreferences(PreferenceManager manager) { + mDweetingPreference = (CheckBoxPreference) manager.findPreference( + getString(R.string.dweeting_checkbox_key)); + Preference dweetingPathPreference = manager.findPreference( + getString(R.string.dweeting_thingname_key)); + dweetingPathPreference.setOnPreferenceChangeListener( + mDweetingPathPreferenceListener); + + SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(this); + updateSummary(dweetingPathPreference, + preferences.getString( + getString(R.string.dweeting_thingname_key), null)); + } + protected void initializeVehicleInterfacePreference(PreferenceManager manager) { mVehicleInterfaceListPreference = (ListPreference) manager.findPreference(getString( @@ -353,6 +381,7 @@ protected void initializeDataSourcePreferences(PreferenceManager manager) { initializeBluetoothPreferences(manager); initializeNetwork(manager); initializeTracePreferences(manager); + initializePhoneSensorPreferences(manager); } protected void initializeBluetoothPreferences(PreferenceManager manager) { @@ -485,6 +514,16 @@ public boolean onPreferenceChange(Preference preference, } }; + private OnPreferenceChangeListener mDweetingPathPreferenceListener = + new OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, + Object newValue) { + String path = (String) newValue; + updateSummary(preference, newValue); + return true; + } + }; + private Preference.OnPreferenceClickListener mTraceFileClickListener = new Preference.OnPreferenceClickListener() { diff --git a/enabler/src/main/java/com/openxc/enabler/StatusFragment.java b/enabler/src/main/java/com/openxc/enabler/StatusFragment.java index a3b1c63a8..1cc70abf2 100644 --- a/enabler/src/main/java/com/openxc/enabler/StatusFragment.java +++ b/enabler/src/main/java/com/openxc/enabler/StatusFragment.java @@ -1,17 +1,17 @@ package com.openxc.enabler; -import java.util.Timer; -import java.util.TimerTask; - +import android.bluetooth.BluetoothAdapter; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -28,7 +28,16 @@ import com.openxc.remote.ViConnectionListener; import com.openxcplatform.enabler.R; +import java.util.Timer; +import java.util.TimerTask; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.app.Activity.RESULT_CANCELED; +import static android.app.Activity.RESULT_OK; + public class StatusFragment extends Fragment { + public static final int LOCATION_PERMISSION_REQUEST_CODE = 1; + private static final int REQUEST_ENABLE_BT = 2; private static String TAG = "StatusFragment"; private TextView mMessageCountView; @@ -46,9 +55,10 @@ public class StatusFragment extends Fragment { private TimerTask mUpdateMessageCountTask; private TimerTask mUpdatePipelineStatusTask; private Timer mTimer; + private Context mContext; private synchronized void updateViInfo() { - if(mVehicleManager != null) { + if (mVehicleManager != null) { // Must run in another thread or we get circular references to // VehicleService -> StatusFragment -> VehicleService and the // callback will just fail silently and be removed forever. @@ -65,7 +75,7 @@ public void run() { mViPlatformView.setText(platform); } }); - } catch(NullPointerException e) { + } catch (NullPointerException e) { // A bit of a hack, should probably use a lock - but // this can happen if this view is being paused and it's // not really a problem. @@ -82,7 +92,7 @@ public void onConnected(final VehicleInterfaceDescriptor descriptor) { } public void onDisconnected() { - if(getActivity() != null) { + if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { public void run() { Log.d(TAG, "VI disconnected"); @@ -97,23 +107,23 @@ public void run() { private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, - IBinder service) { + IBinder service) { Log.i(TAG, "Bound to VehicleManager"); - mVehicleManager = ((VehicleManager.VehicleBinder)service - ).getService(); + mVehicleManager = ((VehicleManager.VehicleBinder) service + ).getService(); try { mVehicleManager.addOnVehicleInterfaceConnectedListener( mConnectionListener); - } catch(VehicleServiceException e) { + } catch (VehicleServiceException e) { Log.e(TAG, "Unable to register VI connection listener", e); } - if(getActivity() == null) { + if (getActivity() == null) { Log.w(TAG, "Status fragment detached from activity"); } - if(mVehicleManager.isViConnected()) { + if (mVehicleManager.isViConnected()) { updateViInfo(); } @@ -122,9 +132,9 @@ public void run() { try { // It's possible that between starting the thread and // this running, the manager has gone away. - if(mVehicleManager != null) { + if (mVehicleManager != null) { mVehicleManager.waitUntilBound(); - if(getActivity() != null) { + if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { public void run() { mServiceNotRunningWarningView.setVisibility(View.GONE); @@ -132,7 +142,7 @@ public void run() { }); } } - } catch(VehicleServiceException e) { + } catch (VehicleServiceException e) { Log.w(TAG, "Unable to connect to VehicleService"); } @@ -153,7 +163,7 @@ mVehicleManager, getActivity(), public synchronized void onServiceDisconnected(ComponentName className) { Log.w(TAG, "VehicleService disconnected unexpectedly"); mVehicleManager = null; - if(getActivity() != null) { + if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { public void run() { mServiceNotRunningWarningView.setVisibility(View.VISIBLE); @@ -166,7 +176,7 @@ public void run() { @Override public void onResume() { super.onResume(); - if(getActivity() != null) { + if (getActivity() != null) { getActivity().bindService( new Intent(getActivity(), VehicleManager.class), mConnection, Context.BIND_AUTO_CREATE); @@ -176,7 +186,7 @@ public void onResume() { @Override public synchronized void onPause() { super.onPause(); - if(mVehicleManager != null) { + if (mVehicleManager != null) { getActivity().unbindService(mConnection); mVehicleManager = null; } @@ -184,7 +194,7 @@ public synchronized void onPause() { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { View v = inflater.inflate(R.layout.status_fragment, container, false); mServiceNotRunningWarningView = v.findViewById(R.id.service_not_running_bar); @@ -202,28 +212,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, new View.OnClickListener() { @Override public void onClick(View view) { - try { - DeviceManager deviceManager = new DeviceManager(getActivity()); - deviceManager.startDiscovery(); - // Re-adding the interface with a null address triggers - // automatic mode 1 time - mVehicleManager.setVehicleInterface( - BluetoothVehicleInterface.class, null); - - // clears the existing explicitly set Bluetooth device. - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences( - getActivity()).edit(); - editor.putString(getString(R.string.bluetooth_mac_key), - getString(R.string.bluetooth_mac_automatic_option)); - editor.commit(); - } catch(BluetoothException e) { - Toast.makeText(getActivity(), - "Bluetooth is disabled, can't search for devices", - Toast.LENGTH_LONG).show(); - } catch(VehicleServiceException e) { - Log.e(TAG, "Unable to enable Bluetooth vehicle interface", e); - } + startBluetoothSearch(); } }); @@ -235,4 +224,59 @@ public void run() { return v; } + + private void startBluetoothSearch() { + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + if (isLocationPermissionGranted() && mBluetoothAdapter.isEnabled()) { + try { + DeviceManager deviceManager = new DeviceManager(getActivity()); + deviceManager.startDiscovery(); + // Re-adding the interface with a null address triggers + // automatic mode 1 time + mVehicleManager.setVehicleInterface( + BluetoothVehicleInterface.class, null); + + // clears the existing explicitly set Bluetooth device. + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences( + getActivity()).edit(); + editor.putString(getString(R.string.bluetooth_mac_key), + getString(R.string.bluetooth_mac_automatic_option)); + editor.commit(); + } catch (BluetoothException e) { + Toast.makeText(getActivity(), + "Bluetooth is disabled, can't search for devices", + Toast.LENGTH_LONG).show(); + } catch (VehicleServiceException e) { + Log.e(TAG, "Unable to enable Bluetooth vehicle interface", e); + } + } else if (!mBluetoothAdapter.isEnabled()) { + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } else if (!isLocationPermissionGranted()) { + requestPermissions(new String[]{ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); + } + } + + private boolean isLocationPermissionGranted() { + return ContextCompat.checkSelfPermission(mContext.getApplicationContext(), ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + this.mContext = context; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_CANCELED) { + Toast.makeText(getActivity(), "Bluetooth needs to be enabled for search.", + Toast.LENGTH_LONG).show(); + } + } + } diff --git a/enabler/src/main/java/com/openxc/enabler/preferences/BluetoothPreferenceManager.java b/enabler/src/main/java/com/openxc/enabler/preferences/BluetoothPreferenceManager.java index de8383ba1..e442692d1 100644 --- a/enabler/src/main/java/com/openxc/enabler/preferences/BluetoothPreferenceManager.java +++ b/enabler/src/main/java/com/openxc/enabler/preferences/BluetoothPreferenceManager.java @@ -1,10 +1,5 @@ package com.openxc.enabler.preferences; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; @@ -20,6 +15,11 @@ import com.openxc.util.SupportSettingsUtils; import com.openxcplatform.enabler.R; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + /** * Enable or disable receiving vehicle data from a Bluetooth vehicle interface. * @@ -52,7 +52,7 @@ * enabled or the OpenXC service first starts, to avoid draining the battery. */ public class BluetoothPreferenceManager extends VehiclePreferenceManager { - private final static String TAG = "BluetoothPreferenceManager"; + private final static String TAG = BluetoothPreferenceManager.class.getSimpleName(); private DeviceManager mBluetoothDeviceManager; private HashMap mDiscoveredDevices = @@ -164,7 +164,7 @@ private void persistCandidateDiscoveredDevices() { Context.MODE_MULTI_PROCESS).edit(); Set candidates = new HashSet(); for(Map.Entry device : mDiscoveredDevices.entrySet()) { - if(device.getValue().startsWith( + if(device.getValue().toUpperCase().startsWith( BluetoothVehicleInterface.DEVICE_NAME_PREFIX)) { candidates.add(device.getKey()); } diff --git a/enabler/src/main/java/com/openxc/enabler/preferences/DweetingPreferenceManager.java b/enabler/src/main/java/com/openxc/enabler/preferences/DweetingPreferenceManager.java new file mode 100644 index 000000000..3eb754d17 --- /dev/null +++ b/enabler/src/main/java/com/openxc/enabler/preferences/DweetingPreferenceManager.java @@ -0,0 +1,84 @@ +package com.openxc.enabler.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import com.buglabs.dweetlib.DweetLib; +import com.openxc.sinks.DweetSink; +import com.openxc.sinks.VehicleDataSink; +import com.openxcplatform.enabler.R; + +/** + * Enable or disable sending of vehicle data to dweet.io. + * + * The thingname to send data is read from the shared + * preferences. + */ +public class DweetingPreferenceManager extends VehiclePreferenceManager { + private final static String TAG = "DweetPreferenceManager"; + private VehicleDataSink mDweeter; + + public DweetingPreferenceManager(Context context) { + super(context); + } + + public void close() { + super.close(); + stopDweeting(); + } + + protected PreferenceListener createPreferenceListener() { + return new PreferenceListener() { + private int[] WATCHED_PREFERENCE_KEY_IDS = { + R.string.dweeting_checkbox_key, + R.string.dweeting_thingname_key + }; + + protected int[] getWatchedPreferenceKeyIds() { + return WATCHED_PREFERENCE_KEY_IDS; + } + + public void readStoredPreferences() { + setDweetingStatus(getPreferences().getBoolean(getString( + R.string.dweeting_checkbox_key), false)); + } + }; + } + + private void setDweetingStatus(boolean enabled) { + Log.i(TAG, "Setting dweet to " + enabled); + SharedPreferences.Editor editor = getPreferences().edit(); + String thingname = getPreferenceString(R.string.dweeting_thingname_key); + if (thingname == null || thingname.equals("")) { + thingname = DweetLib.getInstance(getContext()).getRandomThingName(); + editor.putString(getString(R.string.dweeting_thingname_key), thingname); + editor.putString(getString(R.string.dweeting_thingname_default), thingname); + editor.apply(); + } + if(enabled) { + if(mDweeter != null) { + stopDweeting(); + } + + try { + mDweeter = new DweetSink(getContext(), thingname); + } catch(Exception e) { + Log.w(TAG, "Unable to add dweet sink", e); + return; + } + getVehicleManager().addSink(mDweeter); + + } else { + stopDweeting(); + } + } + + private void stopDweeting() { + if(getVehicleManager() != null){ + Log.d(TAG,"removing Dweet sink"); + getVehicleManager().removeSink(mDweeter); + mDweeter = null; + } + } +} diff --git a/enabler/src/main/java/com/openxc/enabler/preferences/NativeGpsPreferenceManager.java b/enabler/src/main/java/com/openxc/enabler/preferences/NativeGpsPreferenceManager.java index 1876d68eb..db001047f 100644 --- a/enabler/src/main/java/com/openxc/enabler/preferences/NativeGpsPreferenceManager.java +++ b/enabler/src/main/java/com/openxc/enabler/preferences/NativeGpsPreferenceManager.java @@ -14,7 +14,9 @@ public NativeGpsPreferenceManager(Context context) { public void close() { super.close(); - getVehicleManager().setNativeGpsStatus(false); + if(getVehicleManager()!=null) { + getVehicleManager().setNativeGpsStatus(false); + } } protected PreferenceListener createPreferenceListener() { diff --git a/enabler/src/main/java/com/openxc/enabler/preferences/PhoneSensorSourcePreferenceManager.java b/enabler/src/main/java/com/openxc/enabler/preferences/PhoneSensorSourcePreferenceManager.java new file mode 100644 index 000000000..2b06f10b5 --- /dev/null +++ b/enabler/src/main/java/com/openxc/enabler/preferences/PhoneSensorSourcePreferenceManager.java @@ -0,0 +1,74 @@ +package com.openxc.enabler.preferences; + +import android.content.Context; +import android.util.Log; + +import com.openxc.remote.VehicleServiceException; +import com.openxc.sources.PhoneSensorSource; +import com.openxcplatform.enabler.R; + +/** + * Created by vish on 5/28/16. + */ + +public class PhoneSensorSourcePreferenceManager extends VehiclePreferenceManager{ + private final static String TAG = "PhoneSensorSourcePreferenceManager"; + + public PhoneSensorSource mPhoneSensorSource; + + public PhoneSensorSourcePreferenceManager(Context context) { + super(context); + } + + public void close() { + super.close(); + stopSensorCapture(); + } + + @Override + protected PreferenceListener createPreferenceListener() { + return new PreferenceListener() { + private int[] WATCHED_PREFERENCE_KEY_IDS = { + R.string.phone_source_polling_checkbox_key + }; + + protected int[] getWatchedPreferenceKeyIds() { + return WATCHED_PREFERENCE_KEY_IDS; + } + + public void readStoredPreferences() { + setPhoneSensorSourceStatus(getPreferences().getBoolean(getString(R.string.phone_source_polling_checkbox_key),false)); + } + }; + } + + private synchronized void setPhoneSensorSourceStatus(boolean enabled) { + Log.i(TAG, "Setting phone source setting to " + enabled); + if(enabled) { + if(mPhoneSensorSource == null) { + stopSensorCapture(); + + try { + mPhoneSensorSource = new PhoneSensorSource( + getContext()); + } catch(Exception e) { + Log.w(TAG, "Unable to start Phone Sensor Source", e); + return; + } + getVehicleManager().addSource(mPhoneSensorSource); + } else { + Log.d(TAG, "Phone Sensor already activated"); + } + } + else { + stopSensorCapture(); + } + } + + private synchronized void stopSensorCapture() { + if(getVehicleManager() != null && mPhoneSensorSource != null){ + getVehicleManager().removeSource(mPhoneSensorSource); + mPhoneSensorSource = null; + } + } +} diff --git a/enabler/src/main/java/com/openxc/enabler/preferences/PreferenceManagerService.java b/enabler/src/main/java/com/openxc/enabler/preferences/PreferenceManagerService.java index 20c8649c3..b06957467 100644 --- a/enabler/src/main/java/com/openxc/enabler/preferences/PreferenceManagerService.java +++ b/enabler/src/main/java/com/openxc/enabler/preferences/PreferenceManagerService.java @@ -1,9 +1,5 @@ package com.openxc.enabler.preferences; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import android.app.Service; import android.content.ComponentName; import android.content.Context; @@ -16,6 +12,10 @@ import com.openxc.VehicleManager; import com.openxc.remote.VehicleServiceException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class PreferenceManagerService extends Service { private static String TAG = "PreferenceManagerService"; @@ -43,12 +43,14 @@ public void onCreate() { mPreferenceManagers = new ArrayList(); mBluetoothPreferenceManager = new BluetoothPreferenceManager(this); mPreferenceManagers.add(mBluetoothPreferenceManager); + mPreferenceManagers.add(new DweetingPreferenceManager(this)); mPreferenceManagers.add(new FileRecordingPreferenceManager(this)); mPreferenceManagers.add(new GpsOverwritePreferenceManager(this)); mPreferenceManagers.add(new NativeGpsPreferenceManager(this)); mPreferenceManagers.add(new UploadingPreferenceManager(this)); mPreferenceManagers.add(new NetworkPreferenceManager(this)); mPreferenceManagers.add(new TraceSourcePreferenceManager(this)); + mPreferenceManagers.add(new PhoneSensorSourcePreferenceManager(this)); mPreferenceManagers.add(new UsbPreferenceManager(this)); mPreferenceManagers.add(new VehicleInterfacePreferenceManager(this)); } diff --git a/enabler/src/main/play/en-US/whatsnew b/enabler/src/main/play/en-US/whatsnew index 71f844d9d..3cb556001 100644 --- a/enabler/src/main/play/en-US/whatsnew +++ b/enabler/src/main/play/en-US/whatsnew @@ -1 +1,5 @@ +* Can send custom commands from commands screen. +* Now supports BugLabs Dweet.io formats +* Now can include phone sensor data into OpenXC stream. +* Can now connect to BLE Vehicle interfaces. * Properly export VehicleService for use by external apps. diff --git a/enabler/src/main/res/layout/command_request_list_item.xml b/enabler/src/main/res/layout/command_request_list_item.xml new file mode 100644 index 000000000..b0c127c82 --- /dev/null +++ b/enabler/src/main/res/layout/command_request_list_item.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + diff --git a/enabler/src/main/res/layout/diagnostic_request_fragment.xml b/enabler/src/main/res/layout/diagnostic_request_fragment.xml index bbf2a3ef8..999710dbd 100644 --- a/enabler/src/main/res/layout/diagnostic_request_fragment.xml +++ b/enabler/src/main/res/layout/diagnostic_request_fragment.xml @@ -13,6 +13,7 @@ diff --git a/enabler/src/main/res/layout/send_can_message_fragment.xml b/enabler/src/main/res/layout/send_can_message_fragment.xml index b28cea5e3..f0820c7f3 100644 --- a/enabler/src/main/res/layout/send_can_message_fragment.xml +++ b/enabler/src/main/res/layout/send_can_message_fragment.xml @@ -18,6 +18,7 @@ diff --git a/enabler/src/main/res/layout/send_command_message_fragment.xml b/enabler/src/main/res/layout/send_command_message_fragment.xml new file mode 100644 index 000000000..cd49f6da1 --- /dev/null +++ b/enabler/src/main/res/layout/send_command_message_fragment.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +