blob: 34ea1cf7a2a3efc49ff5ad41cfe72ac1570709db [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.car.kitchensink.input;
import static android.hardware.automotive.vehicle.V2_0.SubscribeFlags.EVENTS_FROM_ANDROID;
import static android.hardware.automotive.vehicle.V2_0.SubscribeFlags.EVENTS_FROM_CAR;
import static android.hardware.automotive.vehicle.V2_0.VehicleDisplay.INSTRUMENT_CLUSTER;
import static android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction.ACTION_DOWN;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.HW_KEY_INPUT;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.hardware.automotive.vehicle.V2_0.IVehicle;
import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
import android.hardware.automotive.vehicle.V2_0.IVehicleCallback.Stub;
import android.hardware.automotive.vehicle.V2_0.SubscribeOptions;
import android.hardware.automotive.vehicle.V2_0.VehicleArea;
import android.hardware.automotive.vehicle.V2_0.VehicleDisplay;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyStatus;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import com.android.car.keventreader.EventReaderService;
import com.android.car.keventreader.IEventCallback;
import com.android.car.keventreader.KeypressEvent;
import com.google.android.car.kitchensink.R;
import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Test input event handling to system.
* vehicle hal should have VEHICLE_PROPERTY_HW_KEY_INPUT support for this to work.
*/
public class InputTestFragment extends Fragment {
private static final String TAG = "CAR.INPUT.KS";
private static final Button BREAK_LINE = null;
private final List<View> mButtons = new ArrayList<>();
// This is fake data generation property available only in emulated VHAL implementation.
private static final int sGenerateFakeDataControllingProperty =
0x0666 | VehiclePropertyGroup.VENDOR | VehicleArea.GLOBAL | VehiclePropertyType.MIXED;
// The key press command is sent with the fake data generation property. It's matching the
// command defined in the emulated VHAL implementation.
private static final int sKeyPressCommand = 100;
private IVehicle mVehicle;
private EventReaderService mEventReaderService;
private final IEventCallback.Stub mKeypressEventHandler = new IEventCallback.Stub() {
private String prettyPrint(KeypressEvent event) {
return String.format("Event{source = %s, keycode = %s, key%s}\n",
event.source,
event.keycodeToString(),
event.isKeydown ? "down" : "up");
}
@Override
public void onEvent(KeypressEvent keypressEvent) throws RemoteException {
Log.d(TAG, "received event " + keypressEvent);
synchronized (mInputEventsList) {
mInputEventsList.append(prettyPrint(keypressEvent));
}
}
};
private final IVehicleCallback.Stub mHalKeyEventHandler = new Stub() {
private String prettyPrint(VehiclePropValue event) {
if (event.prop != HW_KEY_INPUT) return "";
if (event.value == null ||
event.value.int32Values == null ||
event.value.int32Values.size() < 2) return "";
return String.format("Event{source = HAL, keycode = %s, key%s}\n",
event.value.int32Values.get(1),
event.value.int32Values.get(0) == ACTION_DOWN ? "down" : "up");
}
@Override
public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) throws RemoteException {
synchronized (mInputEventsList) {
propValues.forEach(vpv -> mInputEventsList.append(prettyPrint(vpv)));
}
}
@Override
public void onPropertySet(VehiclePropValue propValue) {}
@Override
public void onPropertySetError(int errorCode, int propId, int areaId) {}
};
private TextView mInputEventsList;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
mVehicle = IVehicle.getService();
} catch (NoSuchElementException ex) {
throw new RuntimeException("Couldn't connect to " + IVehicle.kInterfaceName, ex);
} catch (RemoteException e) {
throw new RuntimeException("Failed to connect to IVehicle");
}
Log.d(TAG, "Connected to IVehicle service: " + mVehicle);
mEventReaderService = EventReaderService.tryGet();
Log.d(TAG, "Key Event Reader service: " + mEventReaderService);
if (mEventReaderService != null) {
mEventReaderService.registerCallback(mKeypressEventHandler);
}
SubscribeOptions subscribeOption = new SubscribeOptions();
subscribeOption.propId = HW_KEY_INPUT;
subscribeOption.flags = EVENTS_FROM_CAR | EVENTS_FROM_ANDROID;
ArrayList<SubscribeOptions> subscribeOptions = new ArrayList<>();
subscribeOptions.add(subscribeOption);
try {
mVehicle.subscribe(mHalKeyEventHandler, subscribeOptions);
} catch (RemoteException e) {
Log.e(TAG, "failed to connect to VHAL for key events", e);
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.input_test, container, false);
mInputEventsList = view.findViewById(R.id.events_list);
mInputEventsList.setMovementMethod(new ScrollingMovementMethod());
TextView steeringWheelLabel = new TextView(getActivity() /*context*/);
steeringWheelLabel.setText(R.string.steering_wheel);
steeringWheelLabel.setTextSize(getResources().getDimension(R.dimen.car_title2_size));
Collections.addAll(mButtons,
BREAK_LINE,
createButton(R.string.home, KeyEvent.KEYCODE_HOME),
createButton(R.string.volume_up, KeyEvent.KEYCODE_VOLUME_UP),
createButton(R.string.volume_down, KeyEvent.KEYCODE_VOLUME_DOWN),
createButton(R.string.volume_mute, KeyEvent.KEYCODE_VOLUME_MUTE),
createButton(R.string.voice, KeyEvent.KEYCODE_VOICE_ASSIST),
BREAK_LINE,
createButton(R.string.music, KeyEvent.KEYCODE_MUSIC),
createButton(R.string.music_play, KeyEvent.KEYCODE_MEDIA_PLAY),
createButton(R.string.music_stop, KeyEvent.KEYCODE_MEDIA_STOP),
createButton(R.string.next_song, KeyEvent.KEYCODE_MEDIA_NEXT),
createButton(R.string.prev_song, KeyEvent.KEYCODE_MEDIA_PREVIOUS),
createButton(R.string.tune_right, KeyEvent.KEYCODE_CHANNEL_UP),
createButton(R.string.tune_left, KeyEvent.KEYCODE_CHANNEL_DOWN),
BREAK_LINE,
createButton(R.string.call_send, KeyEvent.KEYCODE_CALL),
createButton(R.string.call_end, KeyEvent.KEYCODE_ENDCALL),
BREAK_LINE,
steeringWheelLabel,
BREAK_LINE,
createButton(R.string.sw_left, KeyEvent.KEYCODE_DPAD_LEFT, INSTRUMENT_CLUSTER),
createButton(R.string.sw_right, KeyEvent.KEYCODE_DPAD_RIGHT,
INSTRUMENT_CLUSTER),
createButton(R.string.sw_up, KeyEvent.KEYCODE_DPAD_UP, INSTRUMENT_CLUSTER),
createButton(R.string.sw_down, KeyEvent.KEYCODE_DPAD_DOWN, INSTRUMENT_CLUSTER),
createButton(R.string.sw_center, KeyEvent.KEYCODE_DPAD_CENTER,
INSTRUMENT_CLUSTER),
createButton(R.string.sw_back, KeyEvent.KEYCODE_BACK, INSTRUMENT_CLUSTER)
);
addButtonsToPanel(view.findViewById(R.id.input_buttons), mButtons);
return view;
}
private Button createButton(@StringRes int textResId, int keyCode) {
return createButton(textResId, keyCode, VehicleDisplay.MAIN);
}
private Button createButton(@StringRes int textResId, int keyCode, int targetDisplay) {
Button button = new Button(getContext());
button.setText(getContext().getString(textResId));
button.setTextSize(getResources().getDimension(R.dimen.car_button_text_size));
button.setOnClickListener(v -> onButtonClick(keyCode, targetDisplay));
return button;
}
private void onButtonClick(int keyCode, int targetDisplay) {
VehiclePropValue prop = new VehiclePropValue();
prop.prop = sGenerateFakeDataControllingProperty;
prop.value.int32Values.addAll(Lists.newArrayList(
sKeyPressCommand, HW_KEY_INPUT, keyCode, targetDisplay));
int status;
try {
status = mVehicle.set(prop);
} catch (RemoteException e) {
throw new RuntimeException("Failed to inject key press");
}
if (VehiclePropertyStatus.AVAILABLE != status) {
Toast.makeText(getContext(), "Failed to inject key event, status:" + status,
Toast.LENGTH_LONG).show();
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
mButtons.clear();
if (mEventReaderService != null) {
mEventReaderService.unregisterCallback(mKeypressEventHandler);
}
try {
mVehicle.unsubscribe(mHalKeyEventHandler, HW_KEY_INPUT);
} catch (RemoteException e) {
Log.e(TAG, "failed to remove HAL registration for keypress events", e);
}
}
private void addButtonsToPanel(LinearLayout root, List<View> buttons) {
LinearLayout panel = null;
for (View button : buttons) {
if (button == BREAK_LINE || panel == null) {
panel = new LinearLayout(getContext());
panel.setOrientation(LinearLayout.HORIZONTAL);
root.addView(panel);
} else {
panel.addView(button);
panel.setPadding(0, 10, 10, 0);
}
}
}
}