1 /* 2 * Copyright (C) 2018 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 17 package com.google.android.car.kitchensink.property; 18 19 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21 import static java.lang.Integer.toHexString; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.car.Car; 26 import android.car.VehiclePropertyIds; 27 import android.car.VehiclePropertyType; 28 import android.car.hardware.CarPropertyConfig; 29 import android.car.hardware.CarPropertyValue; 30 import android.car.hardware.property.CarPropertyManager; 31 import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback; 32 import android.car.hardware.property.CarPropertyManager.GetPropertyCallback; 33 import android.car.hardware.property.CarPropertyManager.GetPropertyRequest; 34 import android.car.hardware.property.CarPropertyManager.GetPropertyResult; 35 import android.car.hardware.property.Subscription; 36 import android.content.Context; 37 import android.os.Bundle; 38 import android.os.Handler; 39 import android.util.Log; 40 import android.util.SparseArray; 41 import android.util.SparseBooleanArray; 42 import android.util.SparseIntArray; 43 import android.util.SparseLongArray; 44 import android.view.LayoutInflater; 45 import android.view.View; 46 import android.view.ViewGroup; 47 import android.widget.AdapterView; 48 import android.widget.AdapterView.OnItemSelectedListener; 49 import android.widget.ArrayAdapter; 50 import android.widget.Button; 51 import android.widget.EditText; 52 import android.widget.ScrollView; 53 import android.widget.Spinner; 54 import android.widget.TextView; 55 import android.widget.Toast; 56 import android.widget.ToggleButton; 57 58 import androidx.fragment.app.Fragment; 59 60 import com.google.android.car.kitchensink.KitchenSinkHelper; 61 import com.google.android.car.kitchensink.R; 62 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.List; 66 import java.util.stream.Collectors; 67 68 public class PropertyTestFragment extends Fragment implements OnItemSelectedListener { 69 private static final String TAG = "PropertyTestFragment"; 70 private static final int KS_PERMISSIONS_REQUEST = 1; 71 72 // The dangerous permissions that need to be granted at run-time. 73 private static final String[] REQUIRED_DANGEROUS_PERMISSIONS = new String[]{ 74 Car.PERMISSION_ENERGY, 75 Car.PERMISSION_SPEED 76 }; 77 private static final Float[] SUBSCRIPTION_RATES_HZ = new Float[]{ 78 0.0f, 79 1.0f, 80 2.0f, 81 5.0f, 82 10.0f, 83 100.0f 84 }; 85 private static final Float[] RESOLUTIONS = new Float[]{ 86 0.0f, 87 0.1f, 88 1.0f, 89 10.0f 90 }; 91 92 private Context mContext; 93 private KitchenSinkHelper mKitchenSinkHelper; 94 private CarPropertyManager mMgr; 95 private List<PropertyInfo> mPropInfo = null; 96 private Spinner mSubscriptionRateHz; 97 private Spinner mResolution; 98 private Spinner mVariableUpdateRate; 99 private ToggleButton mSubscribeButton; 100 private Spinner mAreaId; 101 private TextView mEventLog; 102 private Spinner mPropertyId; 103 private ScrollView mScrollView; 104 private EditText mSetValue; 105 private PropertyListEventListener mListener; 106 private final SparseIntArray mPropertySubscriptionRateHzSelection = new SparseIntArray(); 107 private final SparseIntArray mPropertyResolutionSelection = new SparseIntArray(); 108 private final SparseIntArray mPropertyVariableUpdateRateSelection = new SparseIntArray(); 109 private final SparseBooleanArray mPropertyIsSubscribedSelection = new SparseBooleanArray(); 110 private GetPropertyCallback mGetPropertyCallback = new GetPropertyCallback() { 111 @Override 112 public void onSuccess(@NonNull GetPropertyResult<?> getPropertyResult) { 113 int propId = getPropertyResult.getPropertyId(); 114 long timestamp = getPropertyResult.getTimestampNanos(); 115 setTextOnSuccess(propId, timestamp, getPropertyResult.getValue(), 116 CarPropertyValue.STATUS_AVAILABLE); 117 } 118 119 @Override 120 public void onFailure(@NonNull CarPropertyManager.PropertyAsyncError propertyAsyncError) { 121 Log.e(TAG, "Failed to get async VHAL property"); 122 Toast.makeText(mContext, "Failed to get async VHAL property with error code: " 123 + propertyAsyncError.getErrorCode() + " and vendor error code: " 124 + propertyAsyncError.getVendorErrorCode(), Toast.LENGTH_SHORT).show(); 125 } 126 }; 127 128 private CarPropertyManager.SetPropertyCallback mSetPropertyCallback = 129 new CarPropertyManager.SetPropertyCallback() { 130 @Override 131 public void onSuccess( 132 @NonNull CarPropertyManager.SetPropertyResult setPropertyResult) { 133 Toast.makeText(mContext, "Success", Toast.LENGTH_SHORT).show(); 134 } 135 136 @Override 137 public void onFailure( 138 @NonNull CarPropertyManager.PropertyAsyncError propertyAsyncError) { 139 Log.e(TAG, "Failed to get async VHAL property"); 140 Toast.makeText(mContext, "Failed to set async VHAL property with error code: " 141 + propertyAsyncError.getErrorCode() + " and vendor error code: " 142 + propertyAsyncError.getVendorErrorCode(), Toast.LENGTH_SHORT).show(); 143 } 144 }; 145 146 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)147 public void onRequestPermissionsResult(int requestCode, String[] permissions, 148 int[] grantResults) { 149 for (int i = 0; i < grantResults.length; i++) { 150 if (grantResults[i] != PERMISSION_GRANTED) { 151 Log.w(TAG, "Permission: " + permissions[i] + " is not granted, " 152 + "some properties might not be listed"); 153 } 154 } 155 Runnable r = () -> { 156 mMgr = mKitchenSinkHelper.getPropertyManager(); 157 populateConfigList(); 158 159 // Configure dropdown menu for propertyId spinner 160 ArrayAdapter<PropertyInfo> adapter = 161 new ArrayAdapter<PropertyInfo>(mContext, android.R.layout.simple_spinner_item, 162 mPropInfo); 163 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 164 mPropertyId.setAdapter(adapter); 165 mPropertyId.setOnItemSelectedListener(this); 166 }; 167 mKitchenSinkHelper.requestRefreshManager(r, new Handler(getContext().getMainLooper())); 168 } 169 170 @Nullable 171 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)172 public View onCreateView(LayoutInflater inflater, 173 @Nullable ViewGroup container, 174 @Nullable Bundle savedInstanceState) { 175 View view = inflater.inflate(R.layout.property, container, false); 176 // Get resource IDs 177 mSubscriptionRateHz = view.findViewById(R.id.sSubscriptionRate); 178 mResolution = view.findViewById(R.id.sResolution); 179 mVariableUpdateRate = view.findViewById(R.id.sVariableUpdateRate); 180 mAreaId = view.findViewById(R.id.sAreaId); 181 mEventLog = view.findViewById(R.id.tvEventLog); 182 mPropertyId = view.findViewById(R.id.sPropertyId); 183 mScrollView = view.findViewById(R.id.svEventLog); 184 mSetValue = view.findViewById(R.id.etSetPropertyValue); 185 mContext = getActivity(); 186 mListener = new PropertyListEventListener(mEventLog); 187 if (!(mContext instanceof KitchenSinkHelper)) { 188 throw new IllegalStateException( 189 "context does not implement " + KitchenSinkHelper.class.getSimpleName()); 190 } 191 mKitchenSinkHelper = (KitchenSinkHelper) mContext; 192 193 mSubscribeButton = view.findViewById(R.id.tbSubscribeButton); 194 mSubscribeButton.setEnabled(false); 195 196 // Configure listeners for buttons 197 Button b = view.findViewById(R.id.bGetProperty); 198 b.setOnClickListener(v -> { 199 try { 200 PropertyInfo info = (PropertyInfo) mPropertyId.getSelectedItem(); 201 int propId = info.mConfig.getPropertyId(); 202 int areaId = Integer.decode(mAreaId.getSelectedItem().toString()); 203 CarPropertyValue value = mMgr.getProperty(propId, areaId); 204 setTextOnSuccess(propId, value.getTimestamp(), value.getValue(), value.getStatus()); 205 } catch (Exception e) { 206 Log.e(TAG, "Failed to get VHAL property", e); 207 Toast.makeText(mContext, "Failed to get VHAL property: " + e.getMessage(), 208 Toast.LENGTH_SHORT).show(); 209 } 210 }); 211 212 b = view.findViewById(R.id.getPropertyAsync); 213 b.setOnClickListener(v -> { 214 try { 215 PropertyInfo info = (PropertyInfo) mPropertyId.getSelectedItem(); 216 int propId = info.mConfig.getPropertyId(); 217 int areaId = Integer.decode(mAreaId.getSelectedItem().toString()); 218 GetPropertyRequest getPropertyRequest = mMgr.generateGetPropertyRequest(propId, 219 areaId); 220 mMgr.getPropertiesAsync(List.of(getPropertyRequest), 221 /* cancellationSignal= */ null, /* callbackExecutor= */ null, 222 mGetPropertyCallback); 223 } catch (Exception e) { 224 Log.e(TAG, "Failed to get async VHAL property", e); 225 Toast.makeText(mContext, "Failed to get async VHAL property: " 226 + e.getMessage(), Toast.LENGTH_SHORT).show(); 227 } 228 }); 229 230 b = view.findViewById(R.id.bSetProperty); 231 b.setOnClickListener(v -> { 232 try { 233 PropertyInfo info = (PropertyInfo) mPropertyId.getSelectedItem(); 234 int propId = info.mConfig.getPropertyId(); 235 int areaId = Integer.decode(mAreaId.getSelectedItem().toString()); 236 String valueString = mSetValue.getText().toString(); 237 238 switch (propId & VehiclePropertyType.MASK) { 239 case VehiclePropertyType.BOOLEAN: 240 Boolean boolVal = Boolean.parseBoolean(valueString); 241 mMgr.setBooleanProperty(propId, areaId, boolVal); 242 break; 243 case VehiclePropertyType.FLOAT: 244 Float floatVal = Float.parseFloat(valueString); 245 mMgr.setFloatProperty(propId, areaId, floatVal); 246 break; 247 case VehiclePropertyType.INT32: 248 Integer intVal = Integer.parseInt(valueString); 249 mMgr.setIntProperty(propId, areaId, intVal); 250 break; 251 default: 252 Toast.makeText(mContext, "PropertyType=0x" + toHexString(propId 253 & VehiclePropertyType.MASK) + " is not handled!", 254 Toast.LENGTH_LONG).show(); 255 break; 256 } 257 } catch (Exception e) { 258 Log.e(TAG, "Failed to set VHAL property", e); 259 Toast.makeText(mContext, "Failed to set VHAL property: " + e.getMessage(), 260 Toast.LENGTH_LONG).show(); 261 } 262 }); 263 264 b = view.findViewById(R.id.SetPropertyAsync); 265 b.setOnClickListener(v -> { 266 try { 267 PropertyInfo info = (PropertyInfo) mPropertyId.getSelectedItem(); 268 int propId = info.mConfig.getPropertyId(); 269 int areaId = Integer.decode(mAreaId.getSelectedItem().toString()); 270 String valueString = mSetValue.getText().toString(); 271 272 switch (propId & VehiclePropertyType.MASK) { 273 case VehiclePropertyType.BOOLEAN: 274 Boolean boolVal = Boolean.parseBoolean(valueString); 275 callSetPropertiesAsync(propId, areaId, boolVal); 276 break; 277 case VehiclePropertyType.FLOAT: 278 Float floatVal = Float.parseFloat(valueString); 279 callSetPropertiesAsync(propId, areaId, floatVal); 280 break; 281 case VehiclePropertyType.INT32: 282 Integer intVal = Integer.parseInt(valueString); 283 callSetPropertiesAsync(propId, areaId, intVal); 284 break; 285 default: 286 Toast.makeText(mContext, "PropertyType=0x" + toHexString(propId 287 & VehiclePropertyType.MASK) + " is not handled!", 288 Toast.LENGTH_LONG).show(); 289 break; 290 } 291 } catch (Exception e) { 292 Log.e(TAG, "Failed to set VHAL property", e); 293 Toast.makeText(mContext, "Failed to set async VHAL property: " 294 + e.getMessage(), Toast.LENGTH_LONG).show(); 295 } 296 }); 297 298 b = view.findViewById(R.id.bClearLog); 299 b.setOnClickListener(v -> { 300 mEventLog.setText(""); 301 }); 302 303 requestPermissions(REQUIRED_DANGEROUS_PERMISSIONS, KS_PERMISSIONS_REQUEST); 304 305 return view; 306 } 307 populateConfigList()308 private void populateConfigList() { 309 try { 310 mPropInfo = mMgr.getPropertyList() 311 .stream() 312 .map(PropertyInfo::new) 313 .sorted() 314 .collect(Collectors.toList()); 315 } catch (Exception e) { 316 Log.e(TAG, "Unhandled exception in populateConfigList: ", e); 317 } 318 } 319 setEnabledSubscriptionScrollViews(boolean setEnabled)320 private void setEnabledSubscriptionScrollViews(boolean setEnabled) { 321 mSubscriptionRateHz.setEnabled(setEnabled); 322 mResolution.setEnabled(setEnabled); 323 mVariableUpdateRate.setEnabled(setEnabled); 324 } 325 326 // Spinner callbacks onItemSelected(AdapterView<?> parent, View view, int pos, long id)327 public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { 328 PropertyInfo info = (PropertyInfo) parent.getItemAtPosition(pos); 329 int propertyId = info.mPropId; 330 int[] areaIds = info.mConfig.getAreaIds(); 331 List<String> areaIdsString = new ArrayList<String>(); 332 if (areaIds.length == 0) { 333 areaIdsString.add("0x0"); 334 } else { 335 for (int areaId : areaIds) { 336 areaIdsString.add("0x" + toHexString(areaId)); 337 } 338 } 339 340 // Configure dropdown menu for propertyId spinner 341 ArrayAdapter<String> areaIdAdapter = new ArrayAdapter<String>(mContext, 342 android.R.layout.simple_spinner_item, areaIdsString); 343 areaIdAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 344 mAreaId.setAdapter(areaIdAdapter); 345 346 int changeMode = info.mConfig.getChangeMode(); 347 List<String> subscriptionRateHzStrings = new ArrayList<String>(); 348 subscriptionRateHzStrings.add("0 Hz"); 349 List<String> resolutionStrings = new ArrayList<String>(); 350 resolutionStrings.add("0"); 351 List<String> vurStrings = new ArrayList<String>(); 352 vurStrings.add("DISABLED"); 353 354 if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC) { 355 setEnabledSubscriptionScrollViews(false); 356 mSubscribeButton.setEnabled(false); 357 } else if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) { 358 setEnabledSubscriptionScrollViews(false); 359 mSubscribeButton.setEnabled(true); 360 } else if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) { 361 setEnabledSubscriptionScrollViews(true); 362 mSubscribeButton.setEnabled(true); 363 364 float maxSubRate = info.mConfig.getMaxSampleRate(); 365 subscriptionRateHzStrings.add("1 Hz"); 366 if (maxSubRate >= 2.0) { 367 subscriptionRateHzStrings.add("2 Hz"); 368 } 369 if (maxSubRate >= 5.0) { 370 subscriptionRateHzStrings.add("5 Hz"); 371 } 372 if (maxSubRate >= 10.0) { 373 subscriptionRateHzStrings.add("10 Hz"); 374 } 375 if (maxSubRate >= 100.0) { 376 subscriptionRateHzStrings.add("100 Hz"); 377 } 378 379 resolutionStrings.add("0.1"); 380 resolutionStrings.add("1"); 381 resolutionStrings.add("10"); 382 383 vurStrings.add("ENABLED"); 384 } 385 386 if (mPropertySubscriptionRateHzSelection.get(propertyId, -1) == -1) { 387 mPropertySubscriptionRateHzSelection.put(propertyId, 0); 388 mPropertyResolutionSelection.put(propertyId, 0); 389 mPropertyVariableUpdateRateSelection.put(propertyId, 0); 390 mPropertyIsSubscribedSelection.put(propertyId, false); 391 } 392 393 ArrayAdapter<String> subscriptionRateHzAdapter = new ArrayAdapter<String>(mContext, 394 android.R.layout.simple_spinner_item, subscriptionRateHzStrings); 395 subscriptionRateHzAdapter.setDropDownViewResource( 396 android.R.layout.simple_spinner_dropdown_item); 397 mSubscriptionRateHz.setAdapter(subscriptionRateHzAdapter); 398 mSubscriptionRateHz.setSelection(mPropertySubscriptionRateHzSelection.get(propertyId)); 399 mSubscriptionRateHz.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 400 @Override 401 public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) { 402 mPropertySubscriptionRateHzSelection.put(info.mConfig.getPropertyId(), pos); 403 } 404 405 @Override 406 public void onNothingSelected(AdapterView<?> adapterView) { 407 // do nothing. 408 } 409 }); 410 411 ArrayAdapter<String> resolutionAdapter = new ArrayAdapter<String>(mContext, 412 android.R.layout.simple_spinner_item, resolutionStrings); 413 resolutionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 414 mResolution.setAdapter(resolutionAdapter); 415 mResolution.setSelection(mPropertyResolutionSelection.get(propertyId)); 416 mResolution.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 417 @Override 418 public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) { 419 mPropertyResolutionSelection.put(info.mConfig.getPropertyId(), pos); 420 } 421 422 @Override 423 public void onNothingSelected(AdapterView<?> adapterView) { 424 // do nothing. 425 } 426 }); 427 428 ArrayAdapter<String> variableUpdateRateAdapter = new ArrayAdapter<String>(mContext, 429 android.R.layout.simple_spinner_item, vurStrings); 430 variableUpdateRateAdapter.setDropDownViewResource( 431 android.R.layout.simple_spinner_dropdown_item); 432 mVariableUpdateRate.setAdapter(variableUpdateRateAdapter); 433 mVariableUpdateRate.setSelection(mPropertyVariableUpdateRateSelection.get(propertyId)); 434 mVariableUpdateRate.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 435 @Override 436 public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) { 437 mPropertyVariableUpdateRateSelection.put(info.mConfig.getPropertyId(), pos); 438 } 439 440 @Override 441 public void onNothingSelected(AdapterView<?> adapterView) { 442 // do nothing. 443 } 444 }); 445 446 mSubscribeButton.setChecked(mPropertyIsSubscribedSelection.get(propertyId)); 447 if (mSubscribeButton.isChecked()) { 448 setEnabledSubscriptionScrollViews(false); 449 } 450 mSubscribeButton.setOnClickListener(v -> { 451 Float subscriptionRateHz = SUBSCRIPTION_RATES_HZ[ 452 mPropertySubscriptionRateHzSelection.get(propertyId)]; 453 if (mSubscribeButton.isChecked() 454 && (changeMode != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS 455 || subscriptionRateHz != 0.0)) { 456 mListener.addPropertySelectedSubscriptionRateHz(propertyId, subscriptionRateHz); 457 mListener.updatePropertyStartTime(propertyId); 458 mListener.resetEventCountForProperty(propertyId); 459 460 Float resolution = RESOLUTIONS[mPropertyResolutionSelection.get(propertyId)]; 461 boolean variableUpdateRate = 462 mPropertyVariableUpdateRateSelection.get(propertyId) != 0; 463 464 try { 465 mMgr.subscribePropertyEvents(List.of( 466 new Subscription.Builder(propertyId) 467 .setUpdateRateHz(subscriptionRateHz) 468 .setResolution(resolution) 469 .setVariableUpdateRateEnabled(variableUpdateRate) 470 .build()), 471 /* callbackExecutor= */ null, mListener); 472 mPropertyIsSubscribedSelection.put(propertyId, true); 473 setEnabledSubscriptionScrollViews(false); 474 } catch (Exception e) { 475 Log.e(TAG, "Unhandled exception: ", e); 476 } 477 } else { 478 try { 479 mMgr.unsubscribePropertyEvents(propertyId, mListener); 480 mPropertyIsSubscribedSelection.put(propertyId, false); 481 setEnabledSubscriptionScrollViews(true); 482 } catch (Exception e) { 483 Log.e(TAG, "Unhandled exception: ", e); 484 } 485 } 486 }); 487 } 488 onNothingSelected(AdapterView<?> parent)489 public void onNothingSelected(AdapterView<?> parent) { 490 // Another interface callback 491 } 492 scrollEventLogsToBottom()493 public void scrollEventLogsToBottom() { 494 mScrollView.post(new Runnable() { 495 public void run() { 496 mScrollView.fullScroll(View.FOCUS_DOWN); 497 //mListenerScrollView.smoothScrollTo(0, mTextStatus.getBottom()); 498 } 499 }); 500 } 501 setTextOnSuccess(int propId, long timestamp, Object value, int status)502 private void setTextOnSuccess(int propId, long timestamp, Object value, int status) { 503 mEventLog.append("getProperty: "); 504 if (propId == VehiclePropertyIds.WHEEL_TICK) { 505 Object[] ticks = (Object[]) value; 506 mEventLog.append("ElapsedRealtimeNanos=" + timestamp 507 + " [0]=" + (Long) ticks[0] 508 + " [1]=" + (Long) ticks[1] + " [2]=" + (Long) ticks[2] 509 + " [3]=" + (Long) ticks[3] + " [4]=" + (Long) ticks[4]); 510 } else { 511 String valueString = value.getClass().isArray() 512 ? Arrays.toString((Object[]) value) 513 : value.toString(); 514 mEventLog.append("ElapsedRealtimeNanos=" + timestamp 515 + " status=" + status 516 + " value=" + valueString 517 + " read=" + mMgr.getReadPermission(propId) 518 + " write=" + mMgr.getWritePermission(propId)); 519 } 520 mEventLog.append("\n"); 521 scrollEventLogsToBottom(); 522 } 523 callSetPropertiesAsync(int propId, int areaId, T request)524 private <T> void callSetPropertiesAsync(int propId, int areaId, T request) { 525 mMgr.setPropertiesAsync( 526 List.of(mMgr.generateSetPropertyRequest(propId, areaId, request)), 527 /* cancellationSignal= */ null, 528 /* callbackExecutor= */ null, mSetPropertyCallback); 529 } 530 531 private class PropertyListEventListener implements CarPropertyEventCallback { 532 private final TextView mTvLogEvent; 533 private final SparseArray<Float> mPropSubscriptionRateHz = new SparseArray<>(); 534 private final SparseLongArray mStartTime = new SparseLongArray(); 535 private final SparseIntArray mNumEvents = new SparseIntArray(); 536 PropertyListEventListener(TextView logEvent)537 PropertyListEventListener(TextView logEvent) { 538 mTvLogEvent = logEvent; 539 } 540 addPropertySelectedSubscriptionRateHz(Integer propId, Float subscriptionRateHz)541 void addPropertySelectedSubscriptionRateHz(Integer propId, Float subscriptionRateHz) { 542 mPropSubscriptionRateHz.put(propId, subscriptionRateHz); 543 } 544 updatePropertyStartTime(Integer propId)545 void updatePropertyStartTime(Integer propId) { 546 mStartTime.put(propId, System.currentTimeMillis()); 547 } 548 resetEventCountForProperty(Integer propId)549 void resetEventCountForProperty(Integer propId) { 550 mNumEvents.put(propId, 0); 551 } 552 553 @Override onChangeEvent(CarPropertyValue value)554 public void onChangeEvent(CarPropertyValue value) { 555 int propId = value.getPropertyId(); 556 int areaId = value.getAreaId(); 557 558 mNumEvents.put(propId, mNumEvents.get(propId) + 1); 559 560 String valueString = value.getValue().getClass().isArray() 561 ? Arrays.toString((Object[]) value.getValue()) 562 : value.getValue().toString(); 563 564 mTvLogEvent.append(String.format("Event %1$s: elapsedRealtimeNanos=%2$s propId=0x%3$s " 565 + "areaId=0x%4$s name=%5$s status=%6$s value=%7$s", mNumEvents.get(propId), 566 value.getTimestamp(), toHexString(propId), toHexString(areaId), 567 VehiclePropertyIds.toString(propId), value.getStatus(), valueString)); 568 if (mPropSubscriptionRateHz.contains(propId)) { 569 mTvLogEvent.append( 570 String.format(" selected subscription rate (Hz)=%1$s " 571 + "actual subscription rate (Hz)=%2$s\n", 572 mPropSubscriptionRateHz.get(propId), 573 mNumEvents.get(propId) * 1000.0f / (System.currentTimeMillis() 574 - mStartTime.get(propId)))); 575 } else { 576 mTvLogEvent.append("\n"); 577 } 578 scrollEventLogsToBottom(); 579 } 580 581 @Override onErrorEvent(int propId, int areaId)582 public void onErrorEvent(int propId, int areaId) { 583 mTvLogEvent.append("Received error event propId=0x" 584 + VehiclePropertyIds.toString(propId) + ", areaId=0x" + toHexString(areaId)); 585 scrollEventLogsToBottom(); 586 } 587 } 588 } 589