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