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.app.AlarmManager;
22 import android.app.timedetector.GnssTimeSuggestion;
23 import android.app.timedetector.TimeDetector;
24 import android.content.Context;
25 import android.location.Location;
26 import android.location.LocationListener;
27 import android.location.LocationManager;
28 import android.location.LocationManagerInternal;
29 import android.location.LocationRequest;
30 import android.location.LocationTime;
31 import android.os.Binder;
32 import android.os.SystemClock;
33 import android.os.TimestampedValue;
34 import android.util.Log;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.util.DumpUtils;
38 import com.android.server.FgThread;
39 import com.android.server.LocalServices;
40 import com.android.server.SystemService;
41 
42 import java.io.FileDescriptor;
43 import java.io.PrintWriter;
44 import java.time.Duration;
45 
46 /**
47  * Monitors the GNSS time.
48  *
49  * <p>When available, the time is always suggested to the {@link
50  * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
51  * system clock, depending on user settings and what other signals are available.
52  */
53 public final class GnssTimeUpdateService extends Binder {
54     private static final String TAG = "GnssTimeUpdateService";
55     private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
56 
57     /**
58      * Handles the lifecycle events for the GnssTimeUpdateService.
59      */
60     public static class Lifecycle extends SystemService {
61         private GnssTimeUpdateService mService;
62 
Lifecycle(@onNull Context context)63         public Lifecycle(@NonNull Context context) {
64             super(context);
65         }
66 
67         @Override
onStart()68         public void onStart() {
69             mService = new GnssTimeUpdateService(getContext());
70             publishBinderService("gnss_time_update_service", mService);
71         }
72 
73         @Override
onBootPhase(int phase)74         public void onBootPhase(int phase) {
75             // Need to wait for some location providers to be enabled. If done at
76             // PHASE_SYSTEM_SERVICES_READY, error where "gps" provider does not exist could occur.
77             if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
78                 // Initiate location updates. On boot, GNSS might not be available right away.
79                 // Instead of polling GNSS time periodically, passive location updates are enabled.
80                 // Once an update is received, the gnss time will be queried and suggested to
81                 // TimeDetectorService.
82                 mService.requestGnssTimeUpdates();
83             }
84         }
85     }
86 
87     private static final Duration GNSS_TIME_UPDATE_ALARM_INTERVAL = Duration.ofHours(4);
88     private static final String ATTRIBUTION_TAG = "GnssTimeUpdateService";
89 
90     private final Context mContext;
91     private final TimeDetector mTimeDetector;
92     private final AlarmManager mAlarmManager;
93     private final LocationManager mLocationManager;
94     private final LocationManagerInternal mLocationManagerInternal;
95 
96     @Nullable private AlarmManager.OnAlarmListener mAlarmListener;
97     @Nullable private LocationListener mLocationListener;
98     @Nullable private TimestampedValue<Long> mLastSuggestedGnssTime;
99 
100     @VisibleForTesting
GnssTimeUpdateService(@onNull Context context)101     GnssTimeUpdateService(@NonNull Context context) {
102         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
103         mTimeDetector = mContext.getSystemService(TimeDetector.class);
104         mLocationManager = mContext.getSystemService(LocationManager.class);
105         mAlarmManager = mContext.getSystemService(AlarmManager.class);
106         mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class);
107     }
108 
109     /**
110      * Request passive location updates. Such a request will not trigger any active locations or
111      * power usage itself.
112      */
113     @VisibleForTesting
requestGnssTimeUpdates()114     void requestGnssTimeUpdates() {
115         if (D) {
116             Log.d(TAG, "requestGnssTimeUpdates()");
117         }
118 
119         // Location Listener triggers onLocationChanged() when GNSS data is available, so
120         // that the getGnssTimeMillis() function doesn't need to be continuously polled.
121         mLocationListener = new LocationListener() {
122             @Override
123             public void onLocationChanged(Location location) {
124                 if (D) {
125                     Log.d(TAG, "onLocationChanged()");
126                 }
127 
128                 // getGnssTimeMillis() can return null when the Master Location Switch for the
129                 // foreground user is disabled.
130                 LocationTime locationTime = mLocationManagerInternal.getGnssTimeMillis();
131                 if (locationTime != null) {
132                     suggestGnssTime(locationTime);
133                 } else {
134                     if (D) {
135                         Log.d(TAG, "getGnssTimeMillis() returned null");
136                     }
137                 }
138 
139                 mLocationManager.removeUpdates(mLocationListener);
140                 mLocationListener = null;
141 
142                 mAlarmListener = new AlarmManager.OnAlarmListener() {
143                     @Override
144                     public void onAlarm() {
145                         if (D) {
146                             Log.d(TAG, "onAlarm()");
147                         }
148                         mAlarmListener = null;
149                         requestGnssTimeUpdates();
150                     }
151                 };
152 
153                 // Set next alarm to re-enable location updates.
154                 long next = SystemClock.elapsedRealtime()
155                         + GNSS_TIME_UPDATE_ALARM_INTERVAL.toMillis();
156                 mAlarmManager.set(
157                         AlarmManager.ELAPSED_REALTIME_WAKEUP,
158                         next,
159                         TAG,
160                         mAlarmListener,
161                         FgThread.getHandler());
162             }
163         };
164 
165         mLocationManager.requestLocationUpdates(
166                 LocationManager.GPS_PROVIDER,
167                 new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
168                         .setMinUpdateIntervalMillis(0)
169                         .build(),
170                 FgThread.getExecutor(),
171                 mLocationListener);
172     }
173 
174     /**
175      * Convert LocationTime to TimestampedValue. Then suggest TimestampedValue to Time Detector.
176      */
suggestGnssTime(LocationTime locationTime)177     private void suggestGnssTime(LocationTime locationTime) {
178         if (D) {
179             Log.d(TAG, "suggestGnssTime()");
180         }
181         long gnssTime = locationTime.getTime();
182         long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L;
183 
184         TimestampedValue<Long> timeSignal = new TimestampedValue<>(
185                 elapsedRealtimeMs, gnssTime);
186         mLastSuggestedGnssTime = timeSignal;
187 
188         GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
189         mTimeDetector.suggestGnssTime(timeSuggestion);
190     }
191 
192     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)193     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
194         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
195         pw.println("mLastSuggestedGnssTime: " + mLastSuggestedGnssTime);
196         pw.print("state: ");
197         if (mLocationListener != null) {
198             pw.println("time updates enabled");
199         } else {
200             pw.println("alarm enabled");
201         }
202     }
203 }
204