1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.car.kitchensink.vhal;
17 
18 import android.annotation.Nullable;
19 import android.app.AlertDialog;
20 import android.content.Context;
21 import android.hardware.automotive.vehicle.V2_0.IVehicle;
22 import android.hardware.automotive.vehicle.V2_0.StatusCode;
23 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
24 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
25 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
26 import android.os.Bundle;
27 import android.os.RemoteException;
28 import android.util.Log;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.ArrayAdapter;
33 import android.widget.Button;
34 import android.widget.LinearLayout;
35 import android.widget.ListView;
36 import android.widget.TextView;
37 
38 import androidx.fragment.app.Fragment;
39 
40 import com.google.android.car.kitchensink.KitchenSinkActivity;
41 import com.google.android.car.kitchensink.R;
42 
43 import java.util.List;
44 import java.util.Objects;
45 import java.util.stream.Collectors;
46 
47 public class VehicleHalFragment extends Fragment {
48 
49     private static final String TAG = "CAR.VEHICLEHAL.KS";
50 
51     private KitchenSinkActivity mActivity;
52     private ListView mListView;
53 
54     @Nullable
55     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)56     public View onCreateView(LayoutInflater inflater,
57             @Nullable ViewGroup container,
58             @Nullable Bundle savedInstanceState) {
59         View view = inflater.inflate(R.layout.vhal, container, false);
60         mActivity = (KitchenSinkActivity) getHost();
61         mListView = view.findViewById(R.id.hal_prop_list);
62         return view;
63     }
64 
65     @Override
onResume()66     public void onResume() {
67         super.onResume();
68 
69         final boolean retry = true;
70         final IVehicle vehicle;
71         try {
72             vehicle = Objects.requireNonNull(IVehicle.getService(retry));
73         } catch (RemoteException | RuntimeException e) {
74             Log.e(TAG, "unable to retrieve Vehicle HAL service", e);
75             AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
76             builder.setTitle("Vehicle HAL not available")
77                    .setPositiveButton(android.R.string.ok, (x, y) -> { })
78                    .setMessage("In some cases (e.g. SELinux enforcing mode), this UI "
79                             + "is not available for use. Please use the car property UI")
80                    .show();
81             return;
82         }
83 
84         final List<VehiclePropConfig> propConfigList;
85         try {
86             propConfigList = Objects.requireNonNull(vehicle.getAllPropConfigs());
87         } catch (NullPointerException | RemoteException e) {
88             Log.e(TAG, "unable to retrieve prop configs", e);
89             return;
90         }
91 
92         final List<HalPropertyInfo> supportedProperties = propConfigList.stream()
93             .map(HalPropertyInfo::new)
94             .sorted()
95             .collect(Collectors.toList());
96 
97         mListView.setAdapter(new ListAdapter(mActivity, vehicle, supportedProperties));
98     }
99 
100     private static class HalPropertyInfo implements Comparable<HalPropertyInfo> {
101 
102         public final int id;
103         public final String name;
104         public final VehiclePropConfig config;
105 
HalPropertyInfo(VehiclePropConfig config)106         HalPropertyInfo(VehiclePropConfig config) {
107             this.config = config;
108             id = config.prop;
109             name = VehicleProperty.toString(id);
110         }
111 
112         @Override
toString()113         public String toString() {
114             return name;
115         }
116 
117         @Override
equals(Object other)118         public boolean equals(Object other) {
119             return other instanceof HalPropertyInfo && ((HalPropertyInfo) other).id == id;
120         }
121 
122         @Override
hashCode()123         public int hashCode() {
124             return id;
125         }
126 
127         @Override
compareTo(HalPropertyInfo halPropertyInfo)128         public int compareTo(HalPropertyInfo halPropertyInfo) {
129             return name.compareTo(halPropertyInfo.name);
130         }
131 
getValue(IVehicle vehicle)132         public String getValue(IVehicle vehicle) {
133             String result[] = new String[] {"<unknown>"};
134 
135             try {
136                 VehiclePropValue request = new VehiclePropValue();
137                 // TODO: add zones support
138                 request.prop = id;
139 
140                 // NB: this call is synchronous
141                 vehicle.get(request, (status, propValue) -> {
142                     if (status == StatusCode.OK) {
143                         result[0] = propValue.value.toString();
144                     }
145                 });
146             } catch (android.os.RemoteException e) {
147                 Log.e(TAG, "unable to read property " + name, e);
148             }
149 
150             return result[0];
151         }
152     }
153 
154     private static final class ListAdapter extends ArrayAdapter<HalPropertyInfo> {
155         private static final int RESOURCE_ID = R.layout.vhal_listitem;
156 
157         // cannot use superclass' LayoutInflater as it is private
158         private final LayoutInflater mLayoutInflater;
159         private final IVehicle mVehicle;
160 
ListAdapter(Context context, IVehicle vehicle, List<HalPropertyInfo> properties)161         ListAdapter(Context context, IVehicle vehicle, List<HalPropertyInfo> properties) {
162             super(context, RESOURCE_ID, properties);
163             mVehicle = vehicle;
164             mLayoutInflater = LayoutInflater.from(context);
165         }
166 
167         @Override
getView(int position, View convertView, ViewGroup parent)168         public View getView(int position, View convertView, ViewGroup parent) {
169             final HalPropertyInfo item = getItem(position);
170 
171             final LinearLayout viewLayout;
172             if (convertView != null && convertView instanceof LinearLayout) {
173                 viewLayout  = (LinearLayout)convertView;
174             } else {
175                 // this is the value used by the superclass's view inflater
176                 final boolean attachToRoot = false;
177 
178                 viewLayout =
179                         (LinearLayout)mLayoutInflater.inflate(RESOURCE_ID, parent, attachToRoot);
180             }
181 
182             TextView textString = viewLayout.findViewById(R.id.textString);
183             Button infoButton = viewLayout.findViewById(R.id.infoButton);
184             Button valueButton = viewLayout.findViewById(R.id.valueButton);
185 
186             infoButton.setOnClickListener(btn -> {
187                 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
188                 builder.setTitle("Configuration for " + item.name)
189                     .setPositiveButton(android.R.string.yes, (x, y) -> { })
190                     .setMessage(item.config.toString())
191                     .show();
192             });
193 
194             valueButton.setOnClickListener(btn -> {
195                 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
196                 builder.setTitle("Value for " + item.name)
197                     .setPositiveButton(android.R.string.yes, (x, y) -> { })
198                     .setMessage(item.getValue(mVehicle))
199                     .show();
200             });
201 
202             textString.setText(item.toString());
203             return viewLayout;
204         }
205     }
206 }
207