diff --git a/.gitignore b/.gitignore index 6a273b4..c267cae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.iml +output.json .gradle /local.properties /.idea/caches diff --git a/app/build.gradle b/app/build.gradle index b35d961..d5b6fd0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.chif.headsetcontrolplus" minSdkVersion 23 targetSdkVersion 29 - versionCode 12 - versionName "0.2.7" + versionCode 15 + versionName "0.2.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/release/output.json b/app/release/output.json new file mode 100644 index 0000000..acae66f --- /dev/null +++ b/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":15,"versionName":"0.2.9","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/main/java/com/chif/headsetcontrolplus/ForegroundService.java b/app/src/main/java/com/chif/headsetcontrolplus/ForegroundService.java index 477f38d..169d40f 100644 --- a/app/src/main/java/com/chif/headsetcontrolplus/ForegroundService.java +++ b/app/src/main/java/com/chif/headsetcontrolplus/ForegroundService.java @@ -52,14 +52,14 @@ public class ForegroundService extends Service { private static final Handler S_HANDLER = new Handler(); private static AudioManager sAudioManager; - private static int sKeyDownCount = 0; + private static int sKeyUpCount = 0; private final Handler mHandler = new Handler(); private MediaSessionCompat mMediaSessionCompat; private MediaPlayer mMediaPlayer; private ScreenOnOffReceiver mScreenOnOffReceiver; private String mGestureMode = "unknown"; - private Runnable mGestureLongPressed; private Runnable mGestureSinglePressed; + private Runnable mGestureDoublePressed; private Context mContext; private PlaybackStateCompat.Builder mStateBuilder; @@ -83,9 +83,6 @@ public void onCreate() { mMediaSessionCompat.setCallback(new MediaSessionCompat.Callback() { @Override public boolean onMediaButtonEvent(Intent mediaButtonEvent) { - if (mScreenOnOffReceiver.isScreenOn()) { - return super.onMediaButtonEvent(mediaButtonEvent); - } return handleMediaButton(mediaButtonEvent); } }); @@ -175,57 +172,58 @@ private boolean handleMediaButton(final Intent mediaButtonEvent) { return false; } - if (keycode != KeyEvent.KEYCODE_HEADSETHOOK && keycode != KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { - // Not interested in any other key + // Not interested in any other key + if (ServiceBase.isSupportedKey(keycode)) { Log.i(APP_TAG, "Ignored " + keycode); return false; } SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); - // Long Press. - if (action == KeyEvent.ACTION_DOWN) { - mGestureLongPressed = new Runnable() { - public void run() { - mGestureMode = "long_press"; - HeadsetControlPlusService.handleGesture("long"); - Log.w(APP_TAG, "Executed long press Action"); - } - }; - // Start tracking long press. If no action up is detected after 950ms, - // consider ut as long press. - mHandler.postDelayed(mGestureLongPressed, 900); - } - - - // Single and Double Click. + // Single, Double, Triple Click. if (action == KeyEvent.ACTION_UP) { - sKeyDownCount++; - mHandler.removeCallbacks(mGestureLongPressed); - mGestureSinglePressed = new Runnable() { - public void run() { - // Single press. - if (sKeyDownCount == 1) { - // Check if this keyup event is not following a long press event. - if (mGestureMode != "long_press") { + sKeyUpCount++; + + // Single press. + if (sKeyUpCount == 1) { + mGestureSinglePressed = new Runnable() { + public void run() { + if (sKeyUpCount == 1) { + sKeyUpCount = 0; HeadsetControlPlusService.handleGesture("single"); Log.w(APP_TAG, "Executed single press Action"); } - mGestureMode = "unknown"; } - // Double press. - if (sKeyDownCount == 2) { - if (mGestureMode != "long_press") { + }; + S_HANDLER.postDelayed(mGestureSinglePressed, 550); + } + + // Double press. + if (sKeyUpCount == 2) { + S_HANDLER.removeCallbacks(mGestureSinglePressed); + mGestureDoublePressed = new Runnable() { + public void run() { + if (sKeyUpCount == 2) { + sKeyUpCount = 0; HeadsetControlPlusService.handleGesture("double"); Log.w(APP_TAG, "Executed double press Action"); + mGestureMode = "unknown"; } - mGestureMode = "unknown"; + } - sKeyDownCount = 0; + }; + S_HANDLER.postDelayed(mGestureDoublePressed, 500); + } + + // Triple press. + if (sKeyUpCount == 3) { + S_HANDLER.removeCallbacks(mGestureDoublePressed); + if (sKeyUpCount == 3) { + sKeyUpCount = 0; + HeadsetControlPlusService.handleGesture("triple"); + Log.w(APP_TAG, "Executed triple press Action"); + mGestureMode = "unknown"; } - }; - if (sKeyDownCount == 1) { - mHandler.postDelayed(mGestureSinglePressed, 400); } } } @@ -280,9 +278,6 @@ public void onReceive(final Context context, final Intent intent) { mMediaSessionCompat.setCallback(new MediaSessionCompat.Callback() { @Override public boolean onMediaButtonEvent(final Intent mediaButtonEvent) { - if (mScreenOnOffReceiver.isScreenOn()) { - return super.onMediaButtonEvent(mediaButtonEvent); - } return handleMediaButton(mediaButtonEvent); } }); diff --git a/app/src/main/java/com/chif/headsetcontrolplus/HeadsetControlPlusService.java b/app/src/main/java/com/chif/headsetcontrolplus/HeadsetControlPlusService.java index 473377b..973ebcd 100644 --- a/app/src/main/java/com/chif/headsetcontrolplus/HeadsetControlPlusService.java +++ b/app/src/main/java/com/chif/headsetcontrolplus/HeadsetControlPlusService.java @@ -1,19 +1,18 @@ /** * HeadsetControlPlusService.java * -

Copyright 2020 github.com/nadchif + *

Copyright 2020 github.com/nadchif * -

Licensed under the Apache License, Version 2.0 (the "License"); + *

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 + *

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. - * @todo replace long press functionality with triple press */ @@ -32,11 +31,12 @@ import androidx.preference.PreferenceManager; import com.chif.headsetcontrolplus.providers.FlashlightProvider; import com.chif.headsetcontrolplus.providers.StravaProvider; +import com.chif.headsetcontrolplus.shared.ServiceBase; public class HeadsetControlPlusService extends AccessibilityService { private static final String APP_TAG = HeadsetControlPlusService.class.getSimpleName(); private static final Handler S_HANDLER = new Handler(); - private static int sKeyDownCount = 0; + private static int sKeyUpCount = 0; private static String sActionsDefault; private static String sActionsPlayPause; private static String sActionsNext; @@ -51,8 +51,8 @@ public class HeadsetControlPlusService extends AccessibilityService { private static SharedPreferences pref; private static String mGestureMode = "unknown"; private static boolean sIsSimulation = false; - private static Runnable sGestureLongPressed; private static Runnable sGestureSinglePressed; + private static Runnable sGestureDoublePressed; private static FlashlightProvider sFlashlightProvider; private static StravaProvider sStravaProvider; private static Context sContext; @@ -95,7 +95,7 @@ private static void execAction(final String action) { /** * Handles gestures that were polled during screen off. - * @param gesture - Accepts "single", "double", and "long" + * @param gesture - Accepts "single", "double", and "triple" */ public static void handleGesture(final String gesture) { @@ -103,14 +103,14 @@ public static void handleGesture(final String gesture) { sActionsPlayPause); final String doublePressAction = pref.getString("hcp_gestures_double_press", sActionsNext); - final String longPressAction = pref.getString("hcp_gestures_long_press", + final String triplePressAction = pref.getString("hcp_gestures_triple_press", sActionsPrevious); if (gesture == "single") { execAction(singlePressAction); } else if (gesture == "double") { execAction(doublePressAction); - } else if (gesture == "long") { - execAction(doublePressAction); + } else if (gesture == "triple") { + execAction(triplePressAction); } } @@ -170,28 +170,14 @@ private static void simulateDoublePress() { } /** - * Simulates a long press of the headset button. This is necessary for cases where after - * catching the initial long press event and it is assigned to do default, you have to + * Simulates triple press of the headset button. This is necessary for cases where after + * catching the initial triple press event and it is assigned to do default, you have to * re-stage it with the sIsSimulation set to true, to allow the event to go through this service * uninterrupted. + * @todo write Triple Press Simulation function */ - private static void simulateLongPress() { - sIsSimulation = true; // Set to true each time, to allow it go through and be handled by system. - - sAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_HEADSETHOOK)); - sGestureLongPressed = new Runnable() { - public void run() { - sAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_HEADSETHOOK)); - } - }; - // Schedule keyup event after longpress timeout. - - //ideally use ViewConfiguration.get(this).getLongPressTimeout(), but for now will set 1000 - S_HANDLER.postDelayed(sGestureLongPressed, 1000); + private static void simulateTriplePress() { - Log.i(APP_TAG, "hcp simulated long press"); } /* Broadcast a togglepause intent */ @@ -242,7 +228,7 @@ private static void muteVolume() { @Override protected void onServiceConnected() { sAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - sActionsDefault = getString(R.string.pref_button_actions_default); + // sActionsDefault = getString(R.string.pref_button_actions_default); sActionsPlayPause = getString(R.string.pref_button_actions_playpause); sActionsNext = getString(R.string.pref_button_actions_next); sActionsPrevious = getString(R.string.pref_button_actions_previous); @@ -272,9 +258,7 @@ public boolean onKeyEvent(final KeyEvent event) { return false; } - if (keycode != KeyEvent.KEYCODE_HEADSETHOOK - && keycode != KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE - && keycode != KeyEvent.KEYCODE_MEDIA_PLAY) { + if (ServiceBase.isSupportedKey(keycode)) { // Not interested in any other keys Log.i(APP_TAG, "Ignored " + keycode); return false; @@ -290,6 +274,7 @@ public boolean onKeyEvent(final KeyEvent event) { Log.d(APP_TAG, ("Broadcast Key " + keycode)); Intent intent = new Intent(getPackageName()); + intent.putExtra("pressed", keycode); sendBroadcast(intent); @@ -297,65 +282,69 @@ public boolean onKeyEvent(final KeyEvent event) { sActionsPlayPause); final String doublePressAction = pref.getString("hcp_gestures_double_press", sActionsNext); - final String longPressAction = pref.getString("hcp_gestures_long_press", + final String triplePressAction = pref.getString("hcp_gestures_triple_press", sActionsPrevious); - // Long Press. - if (action == KeyEvent.ACTION_DOWN) { - sGestureLongPressed = new Runnable() { - public void run() { - mGestureMode = "long_press"; - Log.i(APP_TAG, "Exec Long Press Action"); - if (longPressAction.equals(sActionsDefault)) { - simulateLongPress(); - } else { - execAction(longPressAction); - } - } - }; - // Start tracking long press. If no action up is detected after 1100ms, - // consider ut as long press. - S_HANDLER.postDelayed(sGestureLongPressed, 1100); - } - - // Single and Double Click. + // Determin Single, Double or Click. if (action == KeyEvent.ACTION_UP) { - sKeyDownCount++; - S_HANDLER.removeCallbacks(sGestureLongPressed); - sGestureSinglePressed = new Runnable() { - public void run() { - // Single press. - if (sKeyDownCount == 1) { - // Check if this keyup event is not following a long press event. - if (mGestureMode != "long_press") { - Log.i(APP_TAG, "Exec Single Press Action"); + sKeyUpCount++; + + // Single press. + if (sKeyUpCount == 1) { + sGestureSinglePressed = new Runnable() { + public void run() { + if (sKeyUpCount == 1) { + sKeyUpCount = 0; + Log.d(APP_TAG, "Exec Single Press Action"); if (singlePressAction.equals(sActionsDefault)) { // Simulate the original event. simulateSinglePress(); } else { execAction(singlePressAction); } + mGestureMode = "unknown"; } - mGestureMode = "unknown"; } - // Double press. - if (sKeyDownCount == 2) { - if (mGestureMode != "long_press") { - Log.i(APP_TAG, "Exec Double Press Action"); + }; + S_HANDLER.postDelayed(sGestureSinglePressed, 500); + } + + // Double press. + if (sKeyUpCount == 2) { + S_HANDLER.removeCallbacks(sGestureSinglePressed); + sGestureDoublePressed = new Runnable() { + public void run() { + if (sKeyUpCount == 2) { + sKeyUpCount = 0; + Log.d(APP_TAG, "Exec Double Press Action"); if (doublePressAction.equals(sActionsDefault)) { // Simulate the original event. simulateDoublePress(); } else { execAction(doublePressAction); } + mGestureMode = "unknown"; } - mGestureMode = "unknown"; + + } + }; + S_HANDLER.postDelayed(sGestureDoublePressed, 500); + } + + // Triple press. + if (sKeyUpCount == 3) { + S_HANDLER.removeCallbacks(sGestureDoublePressed); + if (sKeyUpCount == 3) { + sKeyUpCount = 0; + Log.d(APP_TAG, "Exec Triple Press Action"); + if (doublePressAction.equals(sActionsDefault)) { + // Simulate the original event. + simulateTriplePress(); + } else { + execAction(triplePressAction); } - sKeyDownCount = 0; + mGestureMode = "unknown"; } - }; - if (sKeyDownCount == 1) { - S_HANDLER.postDelayed(sGestureSinglePressed, 400); } } return true; diff --git a/app/src/main/java/com/chif/headsetcontrolplus/SettingsFragment.java b/app/src/main/java/com/chif/headsetcontrolplus/SettingsFragment.java index 263cbd5..0033bb0 100644 --- a/app/src/main/java/com/chif/headsetcontrolplus/SettingsFragment.java +++ b/app/src/main/java/com/chif/headsetcontrolplus/SettingsFragment.java @@ -77,7 +77,7 @@ public void onResume() { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { setPreferencesFromResource(R.xml.main_preferences, rootKey); bindPreferenceSummaryToValue(findPreference("hcp_mode")); - bindPreferenceSummaryToValue(findPreference("hcp_gestures_long_press")); + bindPreferenceSummaryToValue(findPreference("hcp_gestures_triple_press")); bindPreferenceSummaryToValue(findPreference("hcp_gestures_single_press")); bindPreferenceSummaryToValue(findPreference("hcp_gestures_double_press")); mPrefForegroundSwitch = findPreference("enable_hcp_foreground_service"); diff --git a/app/src/main/java/com/chif/headsetcontrolplus/providers/StravaProvider.java b/app/src/main/java/com/chif/headsetcontrolplus/providers/StravaProvider.java index 1cad90e..33ee0a3 100644 --- a/app/src/main/java/com/chif/headsetcontrolplus/providers/StravaProvider.java +++ b/app/src/main/java/com/chif/headsetcontrolplus/providers/StravaProvider.java @@ -11,29 +11,38 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.util.Log; public class StravaProvider { + private static final String APP_TAG = StravaProvider.class.getSimpleName(); private Context mContext; - /** Strava Provider. + /** + * Strava Provider. * Handles interactions between Strava and the app + * * @param context - the Context */ public StravaProvider(final Context context) { this.mContext = context; + } - /** Toggle Record. + /** + * Toggle Record. * Starts an activity if its not recording. * Stops an activity if its already recording. - * @todo Catch activity not found exception */ public void toggleRecord() { Intent intent = new Intent(Intent.ACTION_RUN); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.parse("http://strava.com/nfc/record/toggle")); - mContext.startActivity(intent); + try { + mContext.startActivity(intent); + } catch (Exception ex) { + Log.e(APP_TAG, ex.getMessage()); + } } } diff --git a/app/src/main/java/com/chif/headsetcontrolplus/shared/ServiceBase.java b/app/src/main/java/com/chif/headsetcontrolplus/shared/ServiceBase.java index d6cfc23..15c507f 100644 --- a/app/src/main/java/com/chif/headsetcontrolplus/shared/ServiceBase.java +++ b/app/src/main/java/com/chif/headsetcontrolplus/shared/ServiceBase.java @@ -8,9 +8,9 @@ import android.content.Context; import android.provider.Settings; import android.text.TextUtils; +import android.view.KeyEvent; public class ServiceBase { - /** * Checks if the Accessibility Service is enabled. Returns true if it is * @param context - The Context @@ -41,4 +41,15 @@ public static boolean isAccessibilityServiceEnabled(final Context context, } return false; } + + /** + * Checks if the provided keyCode is to be acknowledged as a headset key. + * @param keyCode - The keyCode + * @return true if the keyCode is headset + */ + public static boolean isSupportedKey(final int keyCode) { + return (keyCode != KeyEvent.KEYCODE_HEADSETHOOK + && keyCode != KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE + && keyCode != KeyEvent.KEYCODE_MEDIA_PLAY); + } } diff --git a/app/src/main/java/com/chif/headsetcontrolplus/slides/IntroSlideSetup.java b/app/src/main/java/com/chif/headsetcontrolplus/slides/IntroSlideSetup.java index ab32f78..61a21f7 100644 --- a/app/src/main/java/com/chif/headsetcontrolplus/slides/IntroSlideSetup.java +++ b/app/src/main/java/com/chif/headsetcontrolplus/slides/IntroSlideSetup.java @@ -12,6 +12,8 @@ import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -25,7 +27,9 @@ public class IntroSlideSetup extends Fragment implements ISlidePolicy { private static final String APP_TAG = "HeadsetControlPlus"; + private static final Handler S_HANDLER = new Handler(); private static final String DOCS_TROUBLESHOOT_MD = "https://github.com/nadchif/headset-control-plus/blob/master/docs/TROUBLESHOOT.md"; + private static Runnable sShowHavingTrouble; private Button mStatusMessageBtn; private boolean mSignalReceived = false; private BroadcastReceiver mBroadcastReceiver; @@ -54,19 +58,41 @@ public void onReceive(Context context, Intent intent) { editor.apply(); // Unregister the broadcast receiver after first successful reception getActivity().unregisterReceiver(mBroadcastReceiver); + // Cancel the Having Trouble runnable + S_HANDLER.removeCallbacks(sShowHavingTrouble); } }; + // This receiver will wait to hear from the accessibility service that, // the headset button has worked getActivity().registerReceiver(mBroadcastReceiver, new IntentFilter(getActivity().getPackageName())); + + // Show a having trouble? message after 30 seconds + sShowHavingTrouble = new Runnable() { + public void run() { + Log.e(APP_TAG, "Ran Having tTrouble"); + if (!mSignalReceived) { + mStatusMessageBtn.setText(R.string.status_headset_setup_trouble); + } + } + }; + S_HANDLER.postDelayed(sShowHavingTrouble, 25000); + return view; } @Override public void onDestroyView() { super.onDestroyView(); + try { + // in case user is closing app + getActivity().unregisterReceiver(mBroadcastReceiver); + S_HANDLER.removeCallbacks(sShowHavingTrouble); + } catch (Exception ex) { + Log.e(APP_TAG, ex.getMessage()); + } } @Override diff --git a/app/src/main/res/layout/fragment_intro_slide_permissions.xml b/app/src/main/res/layout/fragment_intro_slide_permissions.xml index fcc930c..133f001 100644 --- a/app/src/main/res/layout/fragment_intro_slide_permissions.xml +++ b/app/src/main/res/layout/fragment_intro_slide_permissions.xml @@ -6,7 +6,7 @@ android:background="@color/introPermissions" android:gravity="center" android:orientation="vertical" - android:padding="60dp"> + android:padding="48dp"> + android:padding="48dp"> diff --git a/app/src/main/res/layout/fragment_intro_slide_welcome.xml b/app/src/main/res/layout/fragment_intro_slide_welcome.xml index 225dce9..317a72c 100644 --- a/app/src/main/res/layout/fragment_intro_slide_welcome.xml +++ b/app/src/main/res/layout/fragment_intro_slide_welcome.xml @@ -6,7 +6,7 @@ android:background="@color/introPermissions" android:gravity="center" android:orientation="vertical" - android:padding="60dp"> + android:padding="48dp"> Triple Press - - Long Press - - System Default + Flashlight On/Off Play/Pause @@ -101,8 +98,8 @@ Welcome to Headset Control Plus. Unleash the power of your Headset button by custom mapping gestures like: - Long Press, Double Press, Triple Press etc.\n\nThis app does not gather or send any information from your device. For more information view our Privacy Policy.\n\n -Camera Permissions are required for flashlight functionality + Double Press, Triple Press etc.\n\nThis app does not gather or send any information from your device. For more information view our Privacy Policy.\n\nCamera + Permissions are required for flashlight functionality @@ -133,8 +130,8 @@ Camera Permissions are required for flashlight functionality Go!! - Awesome! Now let\'s test the setup of your phone and headset (not all phone/headset are supported) - Connect your headset to the phone and press the headset button to continue, if your setup is supported you will be able to proceed. + Awesome! Now let\'s test the setup of your phone and headset (not all phone/headset are supported) + \n\nConnect your headset to the phone and press the headset button to continue, if your setup is supported you will be able to proceed. @@ -153,7 +150,7 @@ Camera Permissions are required for flashlight functionality - System Default + Play/Pause Next Track Previous Track diff --git a/app/src/main/res/xml/appinfo_preferences.xml b/app/src/main/res/xml/appinfo_preferences.xml index f74b315..1060862 100644 --- a/app/src/main/res/xml/appinfo_preferences.xml +++ b/app/src/main/res/xml/appinfo_preferences.xml @@ -2,7 +2,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> @@ -57,9 +57,9 @@ app:defaultValue="@string/pref_button_actions_previous" app:entries="@array/pref_media_actions" app:entryValues="@array/pref_media_actions" - app:key="hcp_gestures_long_press" + app:key="hcp_gestures_triple_press" app:summary="@string/pref_button_actions_previous" - app:title="@string/pref_button_gestures_long_press" + app:title="@string/pref_button_gestures_triple_press" app:iconSpaceReserved="false" />