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