Loading core/java/android/app/time/ITimeDetectorListener.aidl 0 → 100644 +22 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.time; /** {@hide} */ oneway interface ITimeDetectorListener { void onChange(); } No newline at end of file core/java/android/app/timedetector/ITimeDetectorService.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.app.timedetector; import android.app.time.ExternalTimeSuggestion; import android.app.time.ITimeDetectorListener; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.timedetector.GnssTimeSuggestion; Loading @@ -39,6 +40,9 @@ import android.app.timedetector.TelephonyTimeSuggestion; */ interface ITimeDetectorService { TimeCapabilitiesAndConfig getCapabilitiesAndConfig(); void addListener(ITimeDetectorListener listener); void removeListener(ITimeDetectorListener listener); boolean updateConfiguration(in TimeConfiguration timeConfiguration); void suggestExternalTime( in ExternalTimeSuggestion timeSuggestion); Loading services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java +16 −16 Original line number Diff line number Diff line Loading @@ -53,6 +53,22 @@ public interface ServiceConfigAccessor { @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** * Updates the configuration properties that control a device's time behavior. * * <p>This method returns {@code true} if the configuration was changed, * {@code false} otherwise. */ boolean updateConfiguration( @UserIdInt int userId, @NonNull TimeConfiguration requestedConfiguration); /** * Returns a snapshot of the configuration that controls time zone detector behavior for the * specified user. */ @NonNull ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); /** * Returns the absolute threshold below which the system clock need not be updated. i.e. if * setting the system clock would adjust it by less than this (either backwards or forwards) Loading @@ -75,20 +91,4 @@ public interface ServiceConfigAccessor { */ @NonNull @Origin int[] getOriginPriorities(); /** * Updates the configuration properties that control a device's time behavior. * * <p>This method returns {@code true} if the configuration was changed, * {@code false} otherwise. */ boolean updateConfiguration( @UserIdInt int userId, @NonNull TimeConfiguration requestedConfiguration); /** * Returns a snapshot of the configuration that controls time zone detector behavior for the * specified user. */ @NonNull ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); } services/core/java/com/android/server/timedetector/TimeDetectorService.java +133 −4 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.server.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.time.ExternalTimeSuggestion; import android.app.time.ITimeDetectorListener; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.timedetector.GnssTimeSuggestion; Loading @@ -28,11 +30,17 @@ import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; Loading @@ -51,7 +59,8 @@ import java.util.Objects; * and making calls async, leaving the (consequently more testable) {@link TimeDetectorStrategy} * implementation to deal with the logic around time detection. */ public final class TimeDetectorService extends ITimeDetectorService.Stub { public final class TimeDetectorService extends ITimeDetectorService.Stub implements IBinder.DeathRecipient { static final String TAG = "time_detector"; public static class Lifecycle extends SystemService { Loading Loading @@ -85,6 +94,14 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; /** * Holds the listeners. The key is the {@link IBinder} associated with the listener, the value * is the listener itself. */ @GuardedBy("mListeners") @NonNull private final ArrayMap<IBinder, ITimeDetectorListener> mListeners = new ArrayMap<>(); @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor, Loading @@ -103,6 +120,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when // the configuration changes for any reason. mServiceConfigAccessor.addConfigurationInternalChangeListener( () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread)); } @Override Loading @@ -126,10 +148,117 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } @Override public boolean updateConfiguration(@NonNull TimeConfiguration timeConfiguration) { public boolean updateConfiguration(@NonNull TimeConfiguration configuration) { int callingUserId = mCallerIdentityInjector.getCallingUserId(); return updateConfiguration(callingUserId, configuration); } boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration) { // Resolve constants like USER_CURRENT to the true user ID as needed. int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "updateConfiguration", null); enforceManageTimeDetectorPermission(); Objects.requireNonNull(configuration); final long token = mCallerIdentityInjector.clearCallingIdentity(); try { return mServiceConfigAccessor.updateConfiguration(resolvedUserId, configuration); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } } @Override public void addListener(@NonNull ITimeDetectorListener listener) { enforceManageTimeDetectorPermission(); Objects.requireNonNull(listener); synchronized (mListeners) { IBinder listenerBinder = listener.asBinder(); if (mListeners.containsKey(listenerBinder)) { return; } try { // Ensure the reference to the listener will be removed if the client process dies. listenerBinder.linkToDeath(this, 0 /* flags */); // Only add the listener if we can linkToDeath(). mListeners.put(listenerBinder, listener); } catch (RemoteException e) { Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e); } } } @Override public void removeListener(@NonNull ITimeDetectorListener listener) { enforceManageTimeDetectorPermission(); // TODO(b/172891783) Add actual logic return false; Objects.requireNonNull(listener); synchronized (mListeners) { IBinder listenerBinder = listener.asBinder(); boolean removedListener = false; if (mListeners.remove(listenerBinder) != null) { // Stop listening for the client process to die. listenerBinder.unlinkToDeath(this, 0 /* flags */); removedListener = true; } if (!removedListener) { Slog.w(TAG, "Client asked to remove listener=" + listener + ", but no listeners were removed." + " mListeners=" + mListeners); } } } @Override public void binderDied() { // Should not be used as binderDied(IBinder who) is overridden. Slog.wtf(TAG, "binderDied() called unexpectedly."); } /** * Called when one of the ITimeDetectorListener processes dies before calling * {@link #removeListener(ITimeDetectorListener)}. */ @Override public void binderDied(IBinder who) { synchronized (mListeners) { boolean removedListener = false; final int listenerCount = mListeners.size(); for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) { IBinder listenerBinder = mListeners.keyAt(listenerIndex); if (listenerBinder.equals(who)) { mListeners.removeAt(listenerIndex); removedListener = true; break; } } if (!removedListener) { Slog.w(TAG, "Notified of binder death for who=" + who + ", but did not remove any listeners." + " mListeners=" + mListeners); } } } void handleConfigurationInternalChangedOnHandlerThread() { // Configuration has changed, but each user may have a different view of the configuration. // It's possible that this will cause unnecessary notifications but that shouldn't be a // problem. synchronized (mListeners) { final int listenerCount = mListeners.size(); for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) { ITimeDetectorListener listener = mListeners.valueAt(listenerIndex); try { listener.onChange(); } catch (RemoteException e) { Slog.w(TAG, "Unable to notify listener=" + listener, e); } } } } @Override Loading services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +4 −3 Original line number Diff line number Diff line Loading @@ -196,8 +196,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub boolean updateConfiguration( @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "updateConfiguration", null); // Resolve constants like USER_CURRENT to the true user ID as needed. int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "updateConfiguration", null); enforceManageTimeZoneDetectorPermission(); Loading @@ -205,7 +206,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { return mServiceConfigAccessor.updateConfiguration(userId, configuration); return mServiceConfigAccessor.updateConfiguration(resolvedUserId, configuration); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } Loading Loading
core/java/android/app/time/ITimeDetectorListener.aidl 0 → 100644 +22 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.time; /** {@hide} */ oneway interface ITimeDetectorListener { void onChange(); } No newline at end of file
core/java/android/app/timedetector/ITimeDetectorService.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.app.timedetector; import android.app.time.ExternalTimeSuggestion; import android.app.time.ITimeDetectorListener; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.timedetector.GnssTimeSuggestion; Loading @@ -39,6 +40,9 @@ import android.app.timedetector.TelephonyTimeSuggestion; */ interface ITimeDetectorService { TimeCapabilitiesAndConfig getCapabilitiesAndConfig(); void addListener(ITimeDetectorListener listener); void removeListener(ITimeDetectorListener listener); boolean updateConfiguration(in TimeConfiguration timeConfiguration); void suggestExternalTime( in ExternalTimeSuggestion timeSuggestion); Loading
services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java +16 −16 Original line number Diff line number Diff line Loading @@ -53,6 +53,22 @@ public interface ServiceConfigAccessor { @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** * Updates the configuration properties that control a device's time behavior. * * <p>This method returns {@code true} if the configuration was changed, * {@code false} otherwise. */ boolean updateConfiguration( @UserIdInt int userId, @NonNull TimeConfiguration requestedConfiguration); /** * Returns a snapshot of the configuration that controls time zone detector behavior for the * specified user. */ @NonNull ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); /** * Returns the absolute threshold below which the system clock need not be updated. i.e. if * setting the system clock would adjust it by less than this (either backwards or forwards) Loading @@ -75,20 +91,4 @@ public interface ServiceConfigAccessor { */ @NonNull @Origin int[] getOriginPriorities(); /** * Updates the configuration properties that control a device's time behavior. * * <p>This method returns {@code true} if the configuration was changed, * {@code false} otherwise. */ boolean updateConfiguration( @UserIdInt int userId, @NonNull TimeConfiguration requestedConfiguration); /** * Returns a snapshot of the configuration that controls time zone detector behavior for the * specified user. */ @NonNull ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); }
services/core/java/com/android/server/timedetector/TimeDetectorService.java +133 −4 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.server.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.time.ExternalTimeSuggestion; import android.app.time.ITimeDetectorListener; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.timedetector.GnssTimeSuggestion; Loading @@ -28,11 +30,17 @@ import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.TelephonyTimeSuggestion; import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; Loading @@ -51,7 +59,8 @@ import java.util.Objects; * and making calls async, leaving the (consequently more testable) {@link TimeDetectorStrategy} * implementation to deal with the logic around time detection. */ public final class TimeDetectorService extends ITimeDetectorService.Stub { public final class TimeDetectorService extends ITimeDetectorService.Stub implements IBinder.DeathRecipient { static final String TAG = "time_detector"; public static class Lifecycle extends SystemService { Loading Loading @@ -85,6 +94,14 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; /** * Holds the listeners. The key is the {@link IBinder} associated with the listener, the value * is the listener itself. */ @GuardedBy("mListeners") @NonNull private final ArrayMap<IBinder, ITimeDetectorListener> mListeners = new ArrayMap<>(); @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor, Loading @@ -103,6 +120,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when // the configuration changes for any reason. mServiceConfigAccessor.addConfigurationInternalChangeListener( () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread)); } @Override Loading @@ -126,10 +148,117 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } @Override public boolean updateConfiguration(@NonNull TimeConfiguration timeConfiguration) { public boolean updateConfiguration(@NonNull TimeConfiguration configuration) { int callingUserId = mCallerIdentityInjector.getCallingUserId(); return updateConfiguration(callingUserId, configuration); } boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration) { // Resolve constants like USER_CURRENT to the true user ID as needed. int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "updateConfiguration", null); enforceManageTimeDetectorPermission(); Objects.requireNonNull(configuration); final long token = mCallerIdentityInjector.clearCallingIdentity(); try { return mServiceConfigAccessor.updateConfiguration(resolvedUserId, configuration); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } } @Override public void addListener(@NonNull ITimeDetectorListener listener) { enforceManageTimeDetectorPermission(); Objects.requireNonNull(listener); synchronized (mListeners) { IBinder listenerBinder = listener.asBinder(); if (mListeners.containsKey(listenerBinder)) { return; } try { // Ensure the reference to the listener will be removed if the client process dies. listenerBinder.linkToDeath(this, 0 /* flags */); // Only add the listener if we can linkToDeath(). mListeners.put(listenerBinder, listener); } catch (RemoteException e) { Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e); } } } @Override public void removeListener(@NonNull ITimeDetectorListener listener) { enforceManageTimeDetectorPermission(); // TODO(b/172891783) Add actual logic return false; Objects.requireNonNull(listener); synchronized (mListeners) { IBinder listenerBinder = listener.asBinder(); boolean removedListener = false; if (mListeners.remove(listenerBinder) != null) { // Stop listening for the client process to die. listenerBinder.unlinkToDeath(this, 0 /* flags */); removedListener = true; } if (!removedListener) { Slog.w(TAG, "Client asked to remove listener=" + listener + ", but no listeners were removed." + " mListeners=" + mListeners); } } } @Override public void binderDied() { // Should not be used as binderDied(IBinder who) is overridden. Slog.wtf(TAG, "binderDied() called unexpectedly."); } /** * Called when one of the ITimeDetectorListener processes dies before calling * {@link #removeListener(ITimeDetectorListener)}. */ @Override public void binderDied(IBinder who) { synchronized (mListeners) { boolean removedListener = false; final int listenerCount = mListeners.size(); for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) { IBinder listenerBinder = mListeners.keyAt(listenerIndex); if (listenerBinder.equals(who)) { mListeners.removeAt(listenerIndex); removedListener = true; break; } } if (!removedListener) { Slog.w(TAG, "Notified of binder death for who=" + who + ", but did not remove any listeners." + " mListeners=" + mListeners); } } } void handleConfigurationInternalChangedOnHandlerThread() { // Configuration has changed, but each user may have a different view of the configuration. // It's possible that this will cause unnecessary notifications but that shouldn't be a // problem. synchronized (mListeners) { final int listenerCount = mListeners.size(); for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) { ITimeDetectorListener listener = mListeners.valueAt(listenerIndex); try { listener.onChange(); } catch (RemoteException e) { Slog.w(TAG, "Unable to notify listener=" + listener, e); } } } } @Override Loading
services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +4 −3 Original line number Diff line number Diff line Loading @@ -196,8 +196,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub boolean updateConfiguration( @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "updateConfiguration", null); // Resolve constants like USER_CURRENT to the true user ID as needed. int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "updateConfiguration", null); enforceManageTimeZoneDetectorPermission(); Loading @@ -205,7 +206,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { return mServiceConfigAccessor.updateConfiguration(userId, configuration); return mServiceConfigAccessor.updateConfiguration(resolvedUserId, configuration); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } Loading