1 /* 2 * Copyright (C) 2021 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.android.car.hal; 18 19 import static android.hardware.automotive.vehicle.VehicleProperty.ANDROID_EPOCH_TIME; 20 import static android.hardware.automotive.vehicle.VehicleProperty.EXTERNAL_CAR_TIME; 21 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 24 import static java.util.Objects.requireNonNull; 25 26 import android.annotation.Nullable; 27 import android.app.time.ExternalTimeSuggestion; 28 import android.app.time.TimeManager; 29 import android.car.builtin.util.Slogf; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.hardware.automotive.vehicle.VehiclePropertyStatus; 35 36 import com.android.car.CarLog; 37 import com.android.car.R; 38 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 39 import com.android.car.internal.util.IndentingPrintWriter; 40 import com.android.internal.annotations.GuardedBy; 41 42 import java.io.PrintWriter; 43 import java.time.Instant; 44 import java.util.Collection; 45 import java.util.List; 46 47 /** Writes the Android System time to ANDROID_EPOCH_TIME in the VHAL, if supported. */ 48 public final class TimeHalService extends HalServiceBase { 49 50 private static final int[] SUPPORTED_PROPERTIES = 51 new int[]{ANDROID_EPOCH_TIME, EXTERNAL_CAR_TIME}; 52 53 private final Context mContext; 54 private final VehicleHal mHal; 55 private final TimeManager mTimeManager; 56 57 private final boolean mEnableExternalCarTimeSuggestions; 58 59 private final HalPropValueBuilder mPropValueBuilder; 60 61 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 62 @Override 63 public void onReceive(Context context, Intent intent) { 64 if (!intent.getAction().equals(Intent.ACTION_TIME_CHANGED)) { 65 return; 66 } 67 68 synchronized (mLock) { 69 if (mAndroidTimeSupported) { 70 updateAndroidEpochTimePropertyLocked(System.currentTimeMillis()); 71 } 72 } 73 } 74 }; 75 76 private final Object mLock = new Object(); 77 78 @GuardedBy("mLock") 79 private boolean mReceiverRegistered; 80 81 @GuardedBy("mLock") 82 @Nullable 83 private Instant mLastAndroidTimeReported; 84 85 @GuardedBy("mLock") 86 @Nullable 87 private ExternalTimeSuggestion mLastExternalTimeSuggestion; 88 89 @GuardedBy("mLock") 90 private boolean mAndroidTimeSupported; 91 92 @GuardedBy("mLock") 93 private boolean mExternalCarTimeSupported; 94 TimeHalService(Context context, VehicleHal hal)95 TimeHalService(Context context, VehicleHal hal) { 96 mContext = requireNonNull(context); 97 mHal = requireNonNull(hal); 98 mTimeManager = requireNonNull(context.getSystemService(TimeManager.class)); 99 mEnableExternalCarTimeSuggestions = mContext.getResources().getBoolean( 100 R.bool.config_enableExternalCarTimeToExternalTimeSuggestion); 101 mPropValueBuilder = hal.getHalPropValueBuilder(); 102 } 103 104 @Override init()105 public void init() { 106 synchronized (mLock) { 107 if (mAndroidTimeSupported) { 108 updateAndroidEpochTimePropertyLocked(System.currentTimeMillis()); 109 110 IntentFilter filter = new IntentFilter(Intent.ACTION_TIME_CHANGED); 111 mContext.registerReceiver(mReceiver, filter); 112 mReceiverRegistered = true; 113 Slogf.d(CarLog.TAG_TIME, 114 "Registered BroadcastReceiver for Intent.ACTION_TIME_CHANGED"); 115 } 116 117 if (mExternalCarTimeSupported) { 118 HalPropValue propValue = mHal.get(EXTERNAL_CAR_TIME); 119 suggestExternalTimeLocked(propValue); 120 121 mHal.subscribePropertySafe(this, EXTERNAL_CAR_TIME); 122 Slogf.d(CarLog.TAG_TIME, "Subscribed to VHAL property EXTERNAL_CAR_TIME."); 123 } 124 } 125 } 126 127 @Override release()128 public void release() { 129 synchronized (mLock) { 130 if (mReceiverRegistered) { 131 mContext.unregisterReceiver(mReceiver); 132 mReceiverRegistered = false; 133 } 134 135 mAndroidTimeSupported = false; 136 mLastAndroidTimeReported = null; 137 138 mExternalCarTimeSupported = false; 139 mLastExternalTimeSuggestion = null; 140 } 141 } 142 143 @Override getAllSupportedProperties()144 public int[] getAllSupportedProperties() { 145 return SUPPORTED_PROPERTIES; 146 } 147 148 @Override takeProperties(Collection<HalPropConfig> properties)149 public void takeProperties(Collection<HalPropConfig> properties) { 150 for (HalPropConfig property : properties) { 151 switch (property.getPropId()) { 152 case ANDROID_EPOCH_TIME: 153 synchronized (mLock) { 154 mAndroidTimeSupported = true; 155 } 156 break; 157 case EXTERNAL_CAR_TIME: 158 if (mEnableExternalCarTimeSuggestions) { 159 synchronized (mLock) { 160 mExternalCarTimeSupported = true; 161 } 162 } 163 break; 164 default: 165 break; 166 } 167 } 168 } 169 170 @Override onHalEvents(List<HalPropValue> values)171 public void onHalEvents(List<HalPropValue> values) { 172 synchronized (mLock) { 173 if (!mExternalCarTimeSupported) { 174 return; 175 } 176 177 for (HalPropValue value : values) { 178 if (value.getPropId() == EXTERNAL_CAR_TIME) { 179 suggestExternalTimeLocked(value); 180 break; 181 } 182 } 183 } 184 } 185 186 /** Returns whether the service has detected support for ANDROID_EPOCH_TIME VHAL property. */ isAndroidTimeSupported()187 public boolean isAndroidTimeSupported() { 188 synchronized (mLock) { 189 return mAndroidTimeSupported; 190 } 191 } 192 193 /** Returns whether the service has detected support for EXTERNAL_CAR_TIME VHAL property. */ isExternalCarTimeSupported()194 public boolean isExternalCarTimeSupported() { 195 synchronized (mLock) { 196 return mExternalCarTimeSupported; 197 } 198 } 199 200 @GuardedBy("mLock") updateAndroidEpochTimePropertyLocked(long timeMillis)201 private void updateAndroidEpochTimePropertyLocked(long timeMillis) { 202 HalPropValue propValue = mPropValueBuilder.build(ANDROID_EPOCH_TIME, /* areaId= */ 0, 203 /*timestamp=*/timeMillis, VehiclePropertyStatus.AVAILABLE, 204 /*value=*/timeMillis); 205 206 Slogf.d(CarLog.TAG_TIME, "Writing value %d to property ANDROID_EPOCH_TIME", timeMillis); 207 mHal.set(propValue); 208 mLastAndroidTimeReported = Instant.ofEpochMilli(timeMillis); 209 } 210 211 @GuardedBy("mLock") suggestExternalTimeLocked(HalPropValue value)212 private void suggestExternalTimeLocked(HalPropValue value) { 213 if (value.getPropId() != EXTERNAL_CAR_TIME 214 || value.getStatus() != VehiclePropertyStatus.AVAILABLE) { 215 return; 216 } 217 218 if (value.getInt64ValuesSize() != 1) { 219 Slogf.e(CarLog.TAG_TIME, "Invalid value received for EXTERNAL_CAR_TIME.\n" 220 + " Expected a single element in int64values.\n" 221 + " Received: %s", value.dumpInt64Values()); 222 return; 223 } 224 long epochTime = value.getInt64Value(0); 225 // timestamp is stored in nanoseconds but the suggest API uses milliseconds. 226 long elapsedRealtime = value.getTimestamp() / 1_000_000; 227 228 mLastExternalTimeSuggestion = new ExternalTimeSuggestion(elapsedRealtime, epochTime); 229 230 Slogf.d(CarLog.TAG_TIME, "Sending Time Suggestion: " + mLastExternalTimeSuggestion); 231 mTimeManager.suggestExternalTime(mLastExternalTimeSuggestion); 232 } 233 234 @Override 235 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(PrintWriter printWriter)236 public void dump(PrintWriter printWriter) { 237 IndentingPrintWriter writer = new IndentingPrintWriter(printWriter); 238 writer.println("*ExternalTime HAL*"); 239 writer.increaseIndent(); 240 synchronized (mLock) { 241 if (mAndroidTimeSupported) { 242 writer.printf("mLastAndroidTimeReported: %d millis\n", 243 mLastAndroidTimeReported.toEpochMilli()); 244 } 245 if (mExternalCarTimeSupported) { 246 writer.printf("mLastExternalTimeSuggestion: %s\n", mLastExternalTimeSuggestion); 247 } 248 } 249 writer.decreaseIndent(); 250 writer.flush(); 251 } 252 } 253