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.server.timedetector;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.app.AlarmManager;
23 import android.app.time.UnixEpochTime;
24 import android.content.Context;
25 import android.location.LocationListener;
26 import android.location.LocationManager;
27 import android.location.LocationManagerInternal;
28 import android.location.LocationRequest;
29 import android.location.LocationTime;
30 import android.os.Binder;
31 import android.os.Handler;
32 import android.os.ResultReceiver;
33 import android.os.ShellCallback;
34 import android.os.SystemClock;
35 import android.util.LocalLog;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.util.DumpUtils;
41 import com.android.server.FgThread;
42 import com.android.server.LocalServices;
43 import com.android.server.SystemService;
44 
45 import java.io.FileDescriptor;
46 import java.io.PrintWriter;
47 import java.time.Duration;
48 import java.util.Objects;
49 import java.util.concurrent.Executor;
50 
51 /**
52  * Monitors the GNSS time.
53  *
54  * <p>When available, the time is always suggested to the {@link
55  * com.android.server.timedetector.TimeDetectorInternal} where it may be used to set the device
56  * system clock, depending on user settings and what other signals are available.
57  */
58 public final class GnssTimeUpdateService extends Binder {
59     private static final String TAG = "GnssTimeUpdateService";
60     private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
61 
62     /**
63      * Handles the lifecycle events for the GnssTimeUpdateService.
64      */
65     public static class Lifecycle extends SystemService {
66         private GnssTimeUpdateService mService;
67 
Lifecycle(@onNull Context context)68         public Lifecycle(@NonNull Context context) {
69             super(context);
70         }
71 
72         @Override
onStart()73         public void onStart() {
74             Context context = getContext().createAttributionContext(ATTRIBUTION_TAG);
75             AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
76             LocationManager locationManager = context.getSystemService(LocationManager.class);
77             LocationManagerInternal locationManagerInternal =
78                     LocalServices.getService(LocationManagerInternal.class);
79             TimeDetectorInternal timeDetectorInternal =
80                     LocalServices.getService(TimeDetectorInternal.class);
81 
82             mService = new GnssTimeUpdateService(context, alarmManager, locationManager,
83                     locationManagerInternal, timeDetectorInternal);
84             publishBinderService("gnss_time_update_service", mService);
85         }
86 
87         @Override
onBootPhase(int phase)88         public void onBootPhase(int phase) {
89             // Need to wait for some location providers to be enabled. If done at
90             // PHASE_SYSTEM_SERVICES_READY, error where "gps" provider does not exist could occur.
91             if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
92                 // Initiate location updates. On boot, GNSS might not be available right away.
93                 // Instead of polling GNSS time periodically, passive location updates are enabled.
94                 // Once an update is received, the gnss time will be queried and suggested to
95                 // TimeDetectorService.
96                 mService.startGnssListeningInternal();
97             }
98         }
99     }
100 
101     private static final Duration GNSS_TIME_UPDATE_ALARM_INTERVAL = Duration.ofHours(4);
102     private static final String ATTRIBUTION_TAG = "GnssTimeUpdateService";
103 
104     /**
105      * A log that records the decisions to fetch a GNSS time update.
106      * This is logged in bug reports to assist with debugging issues with GNSS time suggestions.
107      */
108     private final LocalLog mLocalLog = new LocalLog(10, false /* useLocalTimestamps */);
109     /** The executor used for async operations */
110     private final Executor mExecutor = FgThread.getExecutor();
111     /** The handler used for async operations */
112     private final Handler mHandler = FgThread.getHandler();
113 
114     private final Context mContext;
115     private final TimeDetectorInternal mTimeDetectorInternal;
116     private final AlarmManager mAlarmManager;
117     private final LocationManager mLocationManager;
118     private final LocationManagerInternal mLocationManagerInternal;
119 
120 
121     private final Object mLock = new Object();
122     @GuardedBy("mLock") @Nullable private AlarmManager.OnAlarmListener mAlarmListener;
123     @GuardedBy("mLock") @Nullable private LocationListener mLocationListener;
124 
125     @Nullable private volatile UnixEpochTime mLastSuggestedGnssTime;
126 
127     @VisibleForTesting
GnssTimeUpdateService(@onNull Context context, @NonNull AlarmManager alarmManager, @NonNull LocationManager locationManager, @NonNull LocationManagerInternal locationManagerInternal, @NonNull TimeDetectorInternal timeDetectorInternal)128     GnssTimeUpdateService(@NonNull Context context, @NonNull AlarmManager alarmManager,
129             @NonNull LocationManager locationManager,
130             @NonNull LocationManagerInternal locationManagerInternal,
131             @NonNull TimeDetectorInternal timeDetectorInternal) {
132         mContext = Objects.requireNonNull(context);
133         mAlarmManager = Objects.requireNonNull(alarmManager);
134         mLocationManager = Objects.requireNonNull(locationManager);
135         mLocationManagerInternal = Objects.requireNonNull(locationManagerInternal);
136         mTimeDetectorInternal = Objects.requireNonNull(timeDetectorInternal);
137     }
138 
139     /**
140      * Used by {@link com.android.server.timedetector.GnssTimeUpdateServiceShellCommand} to force
141      * the service into GNSS listening mode.
142      */
143     @RequiresPermission(android.Manifest.permission.SET_TIME)
startGnssListening()144     boolean startGnssListening() {
145         mContext.enforceCallingPermission(
146                 android.Manifest.permission.SET_TIME, "Start GNSS listening");
147         mLocalLog.log("startGnssListening() called");
148 
149         final long token = Binder.clearCallingIdentity();
150         try {
151             return startGnssListeningInternal();
152         } finally {
153             Binder.restoreCallingIdentity(token);
154         }
155     }
156 
157     /**
158      * Starts listening for passive location updates. Such a request will not trigger any active
159      * locations or power usage itself. Returns {@code true} if the service is listening after the
160      * method returns and {@code false} otherwise. At present this method only returns {@code false}
161      * if there is no GPS provider on the device.
162      *
163      * <p>If the service is already listening for locations this is a no-op. If the device is in a
164      * "sleeping" state between listening periods then it will return to listening.
165      */
166     @VisibleForTesting
startGnssListeningInternal()167     boolean startGnssListeningInternal() {
168         if (!mLocationManager.hasProvider(LocationManager.GPS_PROVIDER)) {
169             logError("GPS provider does not exist on this device");
170             return false;
171         }
172 
173         synchronized (mLock) {
174             if (mLocationListener != null) {
175                 logDebug("Already listening for GNSS updates");
176                 return true;
177             }
178 
179             // If startGnssListening() is called during manual tests to jump back into location
180             // listening then there will usually be an alarm set.
181             if (mAlarmListener != null) {
182                 mAlarmManager.cancel(mAlarmListener);
183                 mAlarmListener = null;
184             }
185 
186             startGnssListeningLocked();
187             return true;
188         }
189     }
190 
191     @GuardedBy("mLock")
startGnssListeningLocked()192     private void startGnssListeningLocked() {
193         logDebug("startGnssListeningLocked()");
194 
195         // Location Listener triggers onLocationChanged() when GNSS data is available, so
196         // that the getGnssTimeMillis() function doesn't need to be continuously polled.
197         mLocationListener = location -> handleLocationAvailable();
198         mLocationManager.requestLocationUpdates(
199                 LocationManager.GPS_PROVIDER,
200                 new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
201                         .setMinUpdateIntervalMillis(0)
202                         .build(),
203                 mExecutor,
204                 mLocationListener);
205     }
206 
handleLocationAvailable()207     private void handleLocationAvailable() {
208         logDebug("handleLocationAvailable()");
209 
210         // getGnssTimeMillis() can return null when the Master Location Switch for the
211         // foreground user is disabled.
212         LocationTime locationTime = mLocationManagerInternal.getGnssTimeMillis();
213         if (locationTime != null) {
214             String msg = "Passive location time received: " + locationTime;
215             logDebug(msg);
216             mLocalLog.log(msg);
217             suggestGnssTime(locationTime);
218         } else {
219             logDebug("getGnssTimeMillis() returned null");
220         }
221 
222         synchronized (mLock) {
223             if (mLocationListener == null) {
224                 logWarning("mLocationListener unexpectedly null");
225             } else {
226                 mLocationManager.removeUpdates(mLocationListener);
227                 mLocationListener = null;
228             }
229 
230             if (mAlarmListener != null) {
231                 logWarning("mAlarmListener was unexpectedly non-null");
232                 mAlarmManager.cancel(mAlarmListener);
233             }
234 
235             // Set next alarm to re-enable location updates.
236             long next = SystemClock.elapsedRealtime()
237                     + GNSS_TIME_UPDATE_ALARM_INTERVAL.toMillis();
238             mAlarmListener = this::handleAlarmFired;
239             mAlarmManager.set(
240                     AlarmManager.ELAPSED_REALTIME_WAKEUP,
241                     next,
242                     TAG,
243                     mAlarmListener,
244                     mHandler);
245         }
246     }
247 
handleAlarmFired()248     private void handleAlarmFired() {
249         logDebug("handleAlarmFired()");
250 
251         synchronized (mLock) {
252             mAlarmListener = null;
253             startGnssListeningLocked();
254         }
255     }
256 
257     /**
258      * Convert LocationTime to TimestampedValue. Then suggest TimestampedValue to Time Detector.
259      */
suggestGnssTime(LocationTime locationTime)260     private void suggestGnssTime(LocationTime locationTime) {
261         logDebug("suggestGnssTime()");
262 
263         long gnssUnixEpochTimeMillis = locationTime.getUnixEpochTimeMillis();
264         long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L;
265 
266         UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMs, gnssUnixEpochTimeMillis);
267         mLastSuggestedGnssTime = unixEpochTime;
268 
269         GnssTimeSuggestion suggestion = new GnssTimeSuggestion(unixEpochTime);
270         mTimeDetectorInternal.suggestGnssTime(suggestion);
271     }
272 
273     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)274     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
275         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
276         pw.println("mLastSuggestedGnssTime: " + mLastSuggestedGnssTime);
277         synchronized (mLock) {
278             pw.print("state: ");
279             if (mLocationListener != null) {
280                 pw.println("time updates enabled");
281             } else {
282                 pw.println("alarm enabled");
283             }
284         }
285         pw.println("Log:");
286         mLocalLog.dump(pw);
287     }
288 
289     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)290     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
291             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
292         new GnssTimeUpdateServiceShellCommand(this).exec(
293                 this, in, out, err, args, callback, resultReceiver);
294     }
295 
logError(String msg)296     private void logError(String msg) {
297         Log.e(TAG, msg);
298         mLocalLog.log(msg);
299     }
300 
logWarning(String msg)301     private void logWarning(String msg) {
302         Log.w(TAG, msg);
303         mLocalLog.log(msg);
304     }
305 
logDebug(String msg)306     private void logDebug(String msg) {
307         if (D) {
308             Log.d(TAG, msg);
309         }
310     }
311 }
312