Skip to content

Commit

Permalink
Add 'switch to map page' button option (#65)
Browse files Browse the repository at this point in the history
* Add virtual karoo key; Add switch to map page hook

* Add option to switch to map page

* Remove empty line

* Removed call to new api
  • Loading branch information
valterc committed Dec 18, 2022
1 parent 666d449 commit 953062a
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 6 deletions.
26 changes: 20 additions & 6 deletions app/src/main/java/com/valterc/ki2/data/input/KarooKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import android.view.KeyEvent;

/**
* Represents a Karoo hardware key and the corresponding KeyEvent keycode.
* Represents a Karoo hardware or virtual key and the corresponding KeyEvent keycode.
*/
public enum KarooKey{
public enum KarooKey {

/**
* Left top key.
Expand All @@ -30,12 +30,22 @@ public enum KarooKey{
/**
* None, unknown or unassigned key.
*/
NONE(KeyEvent.KEYCODE_UNKNOWN);
NONE(KeyEvent.KEYCODE_UNKNOWN),

/**
* Virtual key representing no action.
*/
VIRTUAL_NONE(10_000),

/**
* Virtual key to switch the ride activity to the map page.
*/
VIRTUAL_SWITCH_TO_MAP_PAGE(VIRTUAL_NONE.keyCode + 1);

public static KarooKey fromKeyCode(int keyCode) {
for (KarooKey s : KarooKey.values()) {
if (s.keyCode == keyCode) {
return s;
for (KarooKey karooKey : values()) {
if (karooKey.keyCode == keyCode) {
return karooKey;
}
}

Expand All @@ -48,6 +58,10 @@ public static KarooKey fromKeyCode(int keyCode) {
this.keyCode = keyCode;
}

public final boolean isVirtual() {
return keyCode >= VIRTUAL_NONE.keyCode;
}

public final int getKeyCode() {
return this.keyCode;
}
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/valterc/ki2/input/InputAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ public class InputAdapter {

private final Context context;
private final HashMap<KarooKey, Long> keyDownTimeMap;
private final VirtualInputAdapter virtualInputAdapter;
private InputManager inputManager;
private Method injectInputMethod;

public InputAdapter(Context context) {
this.context = context;
this.keyDownTimeMap = new HashMap<>();
this.virtualInputAdapter = new VirtualInputAdapter();
initInputManager();
}

Expand Down Expand Up @@ -133,6 +135,11 @@ private void simulateLongKeyPress(KarooKey key, long eventTime) {
}

public void executeKeyEvent(KarooKeyEvent keyEvent) {
if (keyEvent.getKey().isVirtual()) {
virtualInputAdapter.handleVirtualKeyEvent(keyEvent);
return;
}

for (int i = 0; i < keyEvent.getReplicate(); i++) {
long eventTime = SystemClock.uptimeMillis() + (long) ViewConfiguration.getKeyRepeatTimeout() * i;
switch (keyEvent.getAction()) {
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/valterc/ki2/input/InputManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class InputManager {
preferenceToSwitchKeyMap.put("lap", (switchEvent, converter) -> new KarooKeyEvent(KarooKey.BACK, KeyAction.DOUBLE_PRESS, switchEvent.getRepeat()));
preferenceToSwitchKeyMap.put("press_map_graph_zoom_out", (switchEvent, converter) -> new KarooKeyEvent(KarooKey.LEFT, KeyAction.SIMULATE_LONG_PRESS, switchEvent.getRepeat()));
preferenceToSwitchKeyMap.put("press_map_graph_zoom_in", (switchEvent, converter) -> new KarooKeyEvent(KarooKey.RIGHT, KeyAction.SIMULATE_LONG_PRESS, switchEvent.getRepeat()));
preferenceToSwitchKeyMap.put("press_switch_to_map_page", (switchEvent, converter) -> new KarooKeyEvent(KarooKey.VIRTUAL_SWITCH_TO_MAP_PAGE, KeyAction.SINGLE_PRESS, switchEvent.getRepeat()));

/*
* Double press events
Expand Down Expand Up @@ -77,6 +78,12 @@ public class InputManager {
}
return new KarooKeyEvent(KarooKey.BACK, KeyAction.DOUBLE_PRESS, switchEvent.getRepeat());
});
preferenceToSwitchKeyMap.put("hold_short_single_switch_to_map_page", (switchEvent, converter) -> {
if (switchEvent.getCommand() != SwitchCommand.LONG_PRESS_DOWN) {
return null;
}
return new KarooKeyEvent(KarooKey.VIRTUAL_SWITCH_TO_MAP_PAGE, KeyAction.SINGLE_PRESS, switchEvent.getRepeat());
});
}

private final SharedPreferences preferences;
Expand Down
38 changes: 38 additions & 0 deletions app/src/main/java/com/valterc/ki2/input/VirtualInputAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.valterc.ki2.input;

import android.annotation.SuppressLint;
import android.util.Log;

import com.valterc.ki2.data.input.KarooKey;
import com.valterc.ki2.data.input.KarooKeyEvent;
import com.valterc.ki2.karoo.hooks.RideActivityHook;

import java.util.HashMap;
import java.util.function.Consumer;

@SuppressLint("LogNotTimber")
public class VirtualInputAdapter {

private final HashMap<KarooKey, Consumer<KarooKeyEvent>> keyMapping;

public VirtualInputAdapter() {
this.keyMapping = new HashMap<>();
this.keyMapping.put(KarooKey.VIRTUAL_SWITCH_TO_MAP_PAGE, karooKeyEvent -> {
boolean result = RideActivityHook.switchToMapPage();
if (!result) {
Log.w("KI2", "Unable to switch to map page");
}
});
}

public void handleVirtualKeyEvent(KarooKeyEvent keyEvent) {
if (!keyEvent.getKey().isVirtual()) {
return;
}

Consumer<KarooKeyEvent> keyEventConsumer = keyMapping.get(keyEvent.getKey());
if (keyEventConsumer != null) {
keyEventConsumer.accept(keyEvent);
}
}
}
175 changes: 175 additions & 0 deletions app/src/main/java/com/valterc/ki2/karoo/hooks/RideActivityHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,79 @@
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

import com.valterc.ki2.utils.ActivityUtils;
import com.valterc.ki2.utils.ProcessUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import kotlin.Lazy;
import kotlin.LazyKt;

@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressLint("LogNotTimber")
public final class RideActivityHook {

private RideActivityHook() {
}

private static Integer ID_VIEW_PAGER;
private static Field FIELD_PAGE_LIST;

private static final Lazy<Class<? extends Enum>> ENUM_PAGE_TYPE = LazyKt.lazy(() -> {
try {
return (Class<? extends Enum>) Class.forName("io.hammerhead.datamodels.profiles.PageType");
} catch (Exception e) {
Log.w("KI2", "Unable to get page type enum", e);
}

return null;
});

private static final Lazy<Enum<?>> ENUM_PAGE_TYPE_MAP = LazyKt.lazy(() -> {
try {
Class<? extends Enum> classPageTypeEnum = ENUM_PAGE_TYPE.getValue();
return Enum.valueOf(classPageTypeEnum, "MAP");
} catch (Exception e) {
Log.w("KI2", "Unable to get page type map enum", e);
}

return null;
});

private static final Lazy<Field> FIELD_PAGER_PAGE_TYPE = LazyKt.lazy(() -> {
try {
Class<?> classPager = Class.forName("io.hammerhead.datamodels.profiles.Page");
Field[] classPagerFields = classPager.getFields();

for (Field field : classPagerFields) {
if (field.getType() == ENUM_PAGE_TYPE.getValue()) {
field.setAccessible(true);
return field;
}
}
throw new Exception("Page type field not found");
} catch (Exception e) {
Log.w("KI2", "Unable to get page type map enum", e);
}

return null;
});

/**
* Indicates if the running code is inside the Ride activity process.
*
* @return True if the running process is dedicated from the Ride activity, False otherwise.
*/
public static boolean isRideActivityProcess() {
return "io.hammerhead.rideapp:io.hammerhead.rideapp.rideActivityProcess".equals(ProcessUtils.getProcessName());
}
Expand Down Expand Up @@ -42,4 +105,116 @@ public static void tryHandlePreload(Context context) {
}
}

@Nullable
private static ViewPager getActivityViewPager() {
if (!RideActivityHook.isRideActivityProcess()) {
return null;
}

Activity activity = ActivityUtils.getRunningActivity();
if (activity == null) {
return null;
}

if (ID_VIEW_PAGER != null) {
View viewViewPager = activity.findViewById(ID_VIEW_PAGER);
if (viewViewPager instanceof ViewPager) {
return (ViewPager) viewViewPager;
}
}

ViewPager viewPager;
View view = activity.findViewById(android.R.id.content);
if (view instanceof ViewGroup) {
viewPager = tryFindViewPager((ViewGroup) view);
if (viewPager != null) {
ID_VIEW_PAGER = viewPager.getId();
return viewPager;
}
}

return null;
}

private static ViewPager tryFindViewPager(ViewGroup viewGroup) {
Set<ViewGroup> childViewGroups = new HashSet<>();
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View childView = viewGroup.getChildAt(i);
if (childView instanceof ViewPager) {
return (ViewPager) childView;
} else if (childView instanceof ViewGroup) {
childViewGroups.add((ViewGroup) childView);
}
}

for (ViewGroup childView : childViewGroups) {
ViewPager viewPager = tryFindViewPager(childView);
if (viewPager != null) {
return viewPager;
}
}

return null;
}

public static boolean switchToMapPage() {
ViewPager viewPager = getActivityViewPager();
if (viewPager == null) {
Log.d("KI2", "Unable to get view pager");
return false;
}

PagerAdapter viewPagerAdapter = viewPager.getAdapter();
if (viewPagerAdapter == null) {
Log.w("KI2", "View pager adapter is null");
return false;
}

if (FIELD_PAGE_LIST == null) {
Field[] fieldsPagerAdapter = viewPagerAdapter.getClass().getFields();
for (Field field : fieldsPagerAdapter) {
if (Collection.class.isAssignableFrom(field.getType()) &&
field.getGenericType().toString().contains("io.hammerhead.datamodels.profiles.Page")) {
FIELD_PAGE_LIST = field;
}
}
}

if (FIELD_PAGE_LIST == null) {
Log.w("KI2", "Unable to get field with list of pages");
return false;
}

Collection<?> pages;

try {
pages = (Collection<?>) FIELD_PAGE_LIST.get(viewPagerAdapter);
} catch (Exception e) {
Log.e("KI2", "Unable to get pages: " + e);
return false;
}

if (pages == null) {
Log.w("KI2", "List of pages is null");
return false;
}

int index = 0;
for (Object page : pages) {
try {
Object pageType = FIELD_PAGER_PAGE_TYPE.getValue().get(page);
if (pageType == ENUM_PAGE_TYPE_MAP.getValue()) {
viewPager.setCurrentItem(index);
return true;
}
} catch (Exception e) {
Log.w("KI2", "Unable to check page type: " + e);
}

index++;
}

return false;
}

}
6 changes: 6 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<item>Mark Lap</item>
<item>Map/Graph Zoom Out</item>
<item>Map/Graph Zoom In</item>
<item>Switch to map page</item>
</string-array>

<string-array name="preference_values_switch_single_press_action">
Expand All @@ -19,6 +20,7 @@
<item>lap</item>
<item>press_map_graph_zoom_out</item>
<item>press_map_graph_zoom_in</item>
<item>press_switch_to_map_page</item>
</string-array>

<string-array name="preference_titles_switch_double_press_action">
Expand All @@ -30,6 +32,7 @@
<item>Mark Lap</item>
<item>Map/Graph Zoom Out</item>
<item>Map/Graph Zoom In</item>
<item>Switch to map page</item>
<item>Duplicate Single Press</item>
</string-array>

Expand All @@ -42,6 +45,7 @@
<item>lap</item>
<item>press_map_graph_zoom_out</item>
<item>press_map_graph_zoom_in</item>
<item>press_switch_to_map_page</item>
<item>double_press_duplicate_single_press</item>
</string-array>

Expand All @@ -52,6 +56,7 @@
<item>Repeat Single Press</item>
<item>Pause/Resume Ride/Confirm</item>
<item>Mark Lap</item>
<item>Switch to map page</item>
</string-array>

<string-array name="preference_values_switch_hold_action">
Expand All @@ -61,6 +66,7 @@
<item>repeat_single_press</item>
<item>hold_short_single_pause_resume_confirm</item>
<item>hold_short_single_lap</item>
<item>hold_short_single_switch_to_map_page</item>
</string-array>

<string-array name="preference_titles_battery_level_low">
Expand Down

0 comments on commit 953062a

Please sign in to comment.