diff --git a/core/java/android/service/timezone/ITimeZoneProvider.aidl b/core/java/android/service/timezone/ITimeZoneProvider.aidl index 62fa15757857b..793bcc6054a98 100644 --- a/core/java/android/service/timezone/ITimeZoneProvider.aidl +++ b/core/java/android/service/timezone/ITimeZoneProvider.aidl @@ -22,7 +22,6 @@ import android.service.timezone.ITimeZoneProviderManager; * @hide */ oneway interface ITimeZoneProvider { - void setTimeZoneProviderManager(in @nullable ITimeZoneProviderManager manager); - void startUpdates(in long initializationTimeoutMillis); + void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis); void stopUpdates(); } diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java index f2bf176620bd5..d71a8300d9b80 100644 --- a/core/java/android/service/timezone/TimeZoneProviderService.java +++ b/core/java/android/service/timezone/TimeZoneProviderService.java @@ -112,7 +112,18 @@ public abstract class TimeZoneProviderService extends Service { private static final String TAG = "TimeZoneProviderService"; - private final Handler mHandler = BackgroundThread.getHandler(); + /** + * The test command result key indicating whether a command succeeded. Value type: boolean + * @hide + */ + public static final String TEST_COMMAND_RESULT_SUCCESS_KEY = "SUCCESS"; + + /** + * The test command result key for the error message present when {@link + * #TEST_COMMAND_RESULT_SUCCESS_KEY} is false. Value type: string + * @hide + */ + public static final String TEST_COMMAND_RESULT_ERROR_KEY = "ERROR"; /** * The Intent action that the primary location-derived time zone provider service must respond @@ -132,6 +143,8 @@ public abstract class TimeZoneProviderService extends Service { private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper(); + private final Handler mHandler = BackgroundThread.getHandler(); + /** Set by {@link #mHandler} thread. */ @Nullable private ITimeZoneProviderManager mManager; @@ -198,11 +211,22 @@ public abstract class TimeZoneProviderService extends Service { }); } + private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager, + @DurationMillisLong long initializationTimeoutMillis) { + mManager = manager; + onStartUpdates(initializationTimeoutMillis); + } + /** * Starts the provider sending updates. */ public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis); + private void onStopUpdatesInternal() { + onStopUpdates(); + mManager = null; + } + /** * Stops the provider sending updates. */ @@ -210,18 +234,14 @@ public abstract class TimeZoneProviderService extends Service { private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub { - @Override - public void setTimeZoneProviderManager(ITimeZoneProviderManager manager) { + public void startUpdates(@NonNull ITimeZoneProviderManager manager, + @DurationMillisLong long initializationTimeoutMillis) { Objects.requireNonNull(manager); - mHandler.post(() -> TimeZoneProviderService.this.mManager = manager); - } - - public void startUpdates(@DurationMillisLong long initializationTimeoutMillis) { - mHandler.post(() -> onStartUpdates(initializationTimeoutMillis)); + mHandler.post(() -> onStartUpdatesInternal(manager, initializationTimeoutMillis)); } public void stopUpdates() { - mHandler.post(TimeZoneProviderService.this::onStopUpdates); + mHandler.post(TimeZoneProviderService.this::onStopUpdatesInternal); } } } diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java index 0881cd2b0324c..5f744fed13000 100644 --- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java @@ -25,8 +25,8 @@ import static com.android.server.location.timezone.LocationTimeZoneProvider.Prov import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.RemoteCallback; import android.util.IndentingPrintWriter; -import android.util.Slog; import java.time.Duration; import java.util.Objects; @@ -161,6 +161,16 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { mProxy.setRequest(request); } + /** + * Passes the supplied test command to the current proxy. + */ + @Override + void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) { + mThreadingDomain.assertCurrentThread(); + + mProxy.handleTestCommand(testCommand, callback); + } + @Override public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) { synchronized (mSharedLock) { @@ -191,19 +201,4 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { + '}'; } } - - /** - * Passes the supplied simulation / testing event to the current proxy iff the proxy is a - * {@link SimulatedLocationTimeZoneProviderProxy}. If not, the event is logged but discarded. - */ - void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) { - mThreadingDomain.assertCurrentThread(); - - if (!(mProxy instanceof SimulatedLocationTimeZoneProviderProxy)) { - Slog.w(TAG, mProxy + " is not a " + SimulatedLocationTimeZoneProviderProxy.class - + ", event=" + event); - return; - } - ((SimulatedLocationTimeZoneProviderProxy) mProxy).simulate(event); - } } diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java index 7496168499020..396e5b029e9cc 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java +++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java @@ -31,6 +31,7 @@ import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_T import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.RemoteCallback; import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; @@ -117,7 +118,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { - debugLog("onEnvironmentConfigChanged()"); + debugLog("onConfigChanged()"); ConfigurationInternal oldConfig = mCurrentUserConfiguration; ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal(); @@ -553,6 +554,7 @@ class ControllerImpl extends LocationTimeZoneProviderController { } } + @NonNull private static GeolocationTimeZoneSuggestion createUncertainSuggestion(@NonNull String reason) { GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null); suggestion.addDebugInfo(reason); @@ -560,30 +562,42 @@ class ControllerImpl extends LocationTimeZoneProviderController { } /** - * Passes a {@link SimulatedBinderProviderEvent] to the appropriate provider. - * If the provider name does not match a known provider, then the event is logged and discarded. + * Passes a test command to the specified provider. If the provider name does not match a + * known provider, then the command is logged and discarded. */ - void simulateBinderProviderEvent(@NonNull SimulatedBinderProviderEvent event) { + void handleProviderTestCommand( + @NonNull String providerName, @NonNull TestCommand testCommand, + @Nullable RemoteCallback callback) { mThreadingDomain.assertCurrentThread(); - String targetProviderName = event.getProviderName(); + LocationTimeZoneProvider targetProvider = getLocationTimeZoneProvider(providerName); + if (targetProvider == null) { + warnLog("Unable to process test command:" + + " providerName=" + providerName + ", testCommand=" + testCommand); + return; + } + + synchronized (mSharedLock) { + try { + targetProvider.handleTestCommand(testCommand, callback); + } catch (Exception e) { + warnLog("Unable to process test command:" + + " providerName=" + providerName + ", testCommand=" + testCommand, e); + } + } + } + + @Nullable + private LocationTimeZoneProvider getLocationTimeZoneProvider(@NonNull String providerName) { LocationTimeZoneProvider targetProvider; - if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) { + if (Objects.equals(mPrimaryProvider.getName(), providerName)) { targetProvider = mPrimaryProvider; - } else if (Objects.equals(mSecondaryProvider.getName(), targetProviderName)) { + } else if (Objects.equals(mSecondaryProvider.getName(), providerName)) { targetProvider = mSecondaryProvider; } else { - warnLog("Unable to process simulated binder provider event," - + " unknown providerName in event=" + event); - return; + warnLog("Bad providerName=" + providerName); + targetProvider = null; } - if (!(targetProvider instanceof BinderLocationTimeZoneProvider)) { - warnLog("Unable to process simulated binder provider event," - + " provider=" + targetProvider - + " is not a " + BinderLocationTimeZoneProvider.class - + ", event=" + event); - return; - } - ((BinderLocationTimeZoneProvider) targetProvider).simulateBinderProviderEvent(event); + return targetProvider; } } diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java index a23b9d7039d09..880dddf3d91bc 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java @@ -21,7 +21,9 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; +import android.os.RemoteCallback; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.SystemProperties; @@ -40,7 +42,11 @@ import com.android.server.timezonedetector.TimeZoneDetectorService; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.time.Duration; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; /** * A service class that acts as a container for the {@link LocationTimeZoneProviderController}, @@ -116,8 +122,11 @@ public class LocationTimeZoneManagerService extends Binder { static final String PRIMARY_PROVIDER_NAME = "primary"; static final String SECONDARY_PROVIDER_NAME = "secondary"; - private static final String SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX = - "persist.sys.location_tz_simulation_mode."; + static final String PROVIDER_MODE_OVERRIDE_SYSTEM_PROPERTY_PREFIX = "persist.sys.geotz."; + static final String PROVIDER_MODE_SIMULATED = "simulated"; + static final String PROVIDER_MODE_DISABLED = "disabled"; + + private static final long BLOCKING_OP_WAIT_DURATION_MILLIS = Duration.ofSeconds(20).toMillis(); private static final String ATTRIBUTION_TAG = "LocationTimeZoneService"; @@ -187,8 +196,7 @@ public class LocationTimeZoneManagerService extends Binder { } private LocationTimeZoneProvider createPrimaryProvider() { - Resources resources = mContext.getResources(); - if (!resources.getBoolean(R.bool.config_enablePrimaryLocationTimeZoneProvider)) { + if (isDisabled(PRIMARY_PROVIDER_NAME)) { return new NullLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME); } @@ -209,8 +217,7 @@ public class LocationTimeZoneManagerService extends Binder { } private LocationTimeZoneProvider createSecondaryProvider() { - Resources resources = mContext.getResources(); - if (!resources.getBoolean(R.bool.config_enableSecondaryLocationTimeZoneProvider)) { + if (isDisabled(SECONDARY_PROVIDER_NAME)) { return new NullLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME); } @@ -230,9 +237,41 @@ public class LocationTimeZoneManagerService extends Binder { return new BinderLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME, proxy); } - private boolean isInSimulationMode(String providerName) { - return SystemProperties.getBoolean( - SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + providerName, false); + /** Used for bug triage and in tests to simulate provider events. */ + private static boolean isInSimulationMode(String providerName) { + return isProviderModeSetInSystemProperties(providerName, PROVIDER_MODE_SIMULATED); + } + + /** Used for bug triage, tests and experiments to remove a provider. */ + private boolean isDisabled(String providerName) { + return !isProviderEnabledInConfig(providerName) + || isProviderModeSetInSystemProperties(providerName, PROVIDER_MODE_DISABLED); + } + + private boolean isProviderEnabledInConfig(String providerName) { + int providerEnabledConfigId; + switch (providerName) { + case PRIMARY_PROVIDER_NAME: { + providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; + break; + } + case SECONDARY_PROVIDER_NAME: { + providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; + break; + } + default: { + throw new IllegalArgumentException(providerName); + } + } + Resources resources = mContext.getResources(); + return resources.getBoolean(providerEnabledConfigId); + } + + private static boolean isProviderModeSetInSystemProperties( + @NonNull String providerName, @NonNull String mode) { + String systemPropertyProviderMode = SystemProperties.get( + PROVIDER_MODE_OVERRIDE_SYSTEM_PROPERTY_PREFIX + providerName, null); + return Objects.equals(systemPropertyProviderMode, mode); } @Override @@ -244,19 +283,45 @@ public class LocationTimeZoneManagerService extends Binder { } /** - * Asynchronously passes a {@link SimulatedBinderProviderEvent] to the appropriate provider. - * The device must be in simulation mode, otherwise an {@link IllegalStateException} will be - * thrown. + * Passes a {@link TestCommand} to the specified provider and waits for the response. */ - void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) - throws IllegalStateException { - if (!isInSimulationMode(event.getProviderName())) { - throw new IllegalStateException("Use \"setprop " - + SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + event.getProviderName() - + " 1\" and reboot before injecting simulated binder events."); + @NonNull + Bundle handleProviderTestCommand( + @NonNull String providerName, @NonNull TestCommand testCommand) { + enforceManageTimeZoneDetectorPermission(); + + // Because this method blocks and posts work to the threading domain thread, it would cause + // a deadlock if it were called by the threading domain thread. + mThreadingDomain.assertNotCurrentThread(); + + AtomicReference resultReference = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + RemoteCallback remoteCallback = new RemoteCallback(x -> { + resultReference.set(x); + latch.countDown(); + }); + + mThreadingDomain.post(() -> { + synchronized (mSharedLock) { + if (mLocationTimeZoneDetectorController == null) { + remoteCallback.sendResult(null); + return; + } + mLocationTimeZoneDetectorController.handleProviderTestCommand( + providerName, testCommand, remoteCallback); + } + }); + + try { + // Wait, but not indefinitely. + if (!latch.await(BLOCKING_OP_WAIT_DURATION_MILLIS, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("Command did not complete in time"); + } + } catch (InterruptedException e) { + throw new AssertionError(e); } - mThreadingDomain.post( - () -> mLocationTimeZoneDetectorController.simulateBinderProviderEvent(event)); + + return resultReference.get(); } @Override @@ -293,4 +358,10 @@ public class LocationTimeZoneManagerService extends Binder { Slog.w(TAG, msg, t); } } + + private void enforceManageTimeZoneDetectorPermission() { + mContext.enforceCallingPermission( + android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION, + "manage time and time zone detection"); + } } diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java index 7c3b891743cc8..9b8863f741b70 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java @@ -15,13 +15,25 @@ */ package com.android.server.location.timezone; +import static com.android.server.location.timezone.LocationTimeZoneManagerService.PRIMARY_PROVIDER_NAME; +import static com.android.server.location.timezone.LocationTimeZoneManagerService.SECONDARY_PROVIDER_NAME; + +import android.annotation.NonNull; +import android.os.Bundle; import android.os.ShellCommand; import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; /** Implements the shell command interface for {@link LocationTimeZoneManagerService}. */ class LocationTimeZoneManagerShellCommand extends ShellCommand { + private static final List VALID_PROVIDER_NAMES = + Arrays.asList(PRIMARY_PROVIDER_NAME, SECONDARY_PROVIDER_NAME); + + private static final String CMD_SEND_PROVIDER_TEST_COMMAND = "send_provider_test_command"; + private final LocationTimeZoneManagerService mService; LocationTimeZoneManagerShellCommand(LocationTimeZoneManagerService service) { @@ -35,8 +47,8 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { } switch (cmd) { - case "simulate_binder": { - return runSimulateBinderEvent(); + case CMD_SEND_PROVIDER_TEST_COMMAND: { + return runSendProviderTestCommand(); } default: { return handleDefaultCommands(cmd); @@ -44,37 +56,82 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { } } - private int runSimulateBinderEvent() { - PrintWriter outPrintWriter = getOutPrintWriter(); - - SimulatedBinderProviderEvent simulatedProviderBinderEvent; - try { - simulatedProviderBinderEvent = SimulatedBinderProviderEvent.createFromArgs(this); - } catch (IllegalArgumentException e) { - outPrintWriter.println("Error: " + e.getMessage()); - return 1; - } - - outPrintWriter.println("Injecting: " + simulatedProviderBinderEvent); - try { - mService.simulateBinderProviderEvent(simulatedProviderBinderEvent); - } catch (IllegalStateException e) { - outPrintWriter.println("Error: " + e.getMessage()); - return 2; - } - return 0; - } - @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); pw.println("Location Time Zone Manager (location_time_zone_manager) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" simulate_binder"); - pw.println(" "); + pw.printf(" %s \n", CMD_SEND_PROVIDER_TEST_COMMAND); + pw.println(" Passes a test command to the named provider."); pw.println(); - SimulatedBinderProviderEvent.printCommandLineOpts(pw); + pw.printf("%s details:\n", CMD_SEND_PROVIDER_TEST_COMMAND); + pw.println(); + pw.printf(" = One of %s\n", VALID_PROVIDER_NAMES); + pw.println(); + pw.println(" encoding:"); + pw.println(); + TestCommand.printShellCommandEncodingHelp(pw); + pw.println(); + pw.printf("Provider modes can be modified by setting the \"%s\" system" + + " property and restarting the service or rebooting the device.\n", + LocationTimeZoneManagerService.PROVIDER_MODE_OVERRIDE_SYSTEM_PROPERTY_PREFIX); + pw.println("Values are:"); + pw.printf(" %s - simulation mode (see below for commands)\n", + LocationTimeZoneManagerService.PROVIDER_MODE_SIMULATED); + pw.printf(" %s - disabled mode\n", LocationTimeZoneManagerService.PROVIDER_MODE_DISABLED); + pw.println(); + pw.println("Simulated providers can be used to test the system server behavior or to" + + " reproduce bugs without the complexity of using real providers."); + pw.println(); + pw.println("The test commands for simulated providers are:"); + SimulatedLocationTimeZoneProviderProxy.printTestCommandShellHelp(pw); + pw.println(); + pw.println("Test commands cannot currently be passed to real provider implementations."); pw.println(); } + + private int runSendProviderTestCommand() { + PrintWriter outPrintWriter = getOutPrintWriter(); + + String providerName; + TestCommand testCommand; + try { + providerName = validateProviderName(getNextArgRequired()); + testCommand = createTestCommandFromNextShellArg(); + } catch (RuntimeException e) { + reportError(e); + return 1; + } + + outPrintWriter.println("Injecting testCommand=" + testCommand + + " to providerName=" + providerName); + try { + Bundle result = mService.handleProviderTestCommand(providerName, testCommand); + outPrintWriter.println(result); + } catch (RuntimeException e) { + reportError(e); + return 2; + } + return 0; + } + + @NonNull + private TestCommand createTestCommandFromNextShellArg() { + return TestCommand.createFromShellCommandArgs(this); + } + + private void reportError(Throwable e) { + PrintWriter errPrintWriter = getErrPrintWriter(); + errPrintWriter.println("Error: "); + e.printStackTrace(errPrintWriter); + } + + @NonNull + static String validateProviderName(@NonNull String value) { + if (!VALID_PROVIDER_NAMES.contains(value)) { + throw new IllegalArgumentException("Unknown provider name=" + value); + } + return value; + } } diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java index e55d1cc74b7bd..d25e4a866a398 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java @@ -16,6 +16,9 @@ package com.android.server.location.timezone; +import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY; +import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; + import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog; import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; @@ -31,7 +34,9 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Bundle; import android.os.Handler; +import android.os.RemoteCallback; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; @@ -493,6 +498,21 @@ abstract class LocationTimeZoneProvider implements Dumpable { */ abstract void onStopUpdates(); + /** + * Overridden by subclasses to handle the supplied {@link TestCommand}. If {@code callback} is + * non-null, the default implementation sends a result {@link Bundle} with {@link + * android.service.timezone.TimeZoneProviderService#TEST_COMMAND_RESULT_SUCCESS_KEY} set to + * {@code false} and a "Not implemented" error message. + */ + void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) { + if (callback != null) { + Bundle result = new Bundle(); + result.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false); + result.putString(TEST_COMMAND_RESULT_ERROR_KEY, "Not implemented"); + callback.sendResult(result); + } + } + /** For subclasses to invoke when a {@link TimeZoneProviderEvent} has been received. */ final void handleTimeZoneProviderEvent(@NonNull TimeZoneProviderEvent timeZoneProviderEvent) { mThreadingDomain.assertCurrentThread(); diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java index 16f9e97bb52e3..0937b3eb46bdd 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.os.Handler; +import android.os.RemoteCallback; import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; @@ -93,6 +94,13 @@ abstract class LocationTimeZoneProviderProxy implements Dumpable { */ abstract void setRequest(@NonNull TimeZoneProviderRequest request); + /** + * Processes the supplied test command. An optional callback can be supplied to listen for a + * response. + */ + abstract void handleTestCommand(@NonNull TestCommand testCommand, + @Nullable RemoteCallback callback); + /** * Handles a {@link TimeZoneProviderEvent} from a remote process. */ diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java index 231136bc91f91..ca8109586f45d 100644 --- a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java @@ -17,6 +17,8 @@ package com.android.server.location.timezone; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY; +import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog; @@ -28,9 +30,10 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; +import android.os.RemoteCallback; import android.service.timezone.ITimeZoneProvider; import android.service.timezone.ITimeZoneProviderManager; import android.service.timezone.TimeZoneProviderSuggestion; @@ -118,18 +121,15 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { private void onBind(IBinder binder, ComponentName componentName) { mThreadingDomain.assertCurrentThread(); - ITimeZoneProvider provider = ITimeZoneProvider.Stub.asInterface(binder); - synchronized (mSharedLock) { - try { - mManagerProxy = new ManagerProxy(); - provider.setTimeZoneProviderManager(mManagerProxy); - trySendCurrentRequest(); - mListener.onProviderBound(); - } catch (RemoteException e) { - // This is not expected to happen. - throw new RuntimeException(e); - } + // When a new remote is first bound we create the ManagerProxy that will be passed to + // it. By creating a new one for each bind the ManagerProxy can check whether it is + // still the current proxy and if not it can ignore incoming calls. + mManagerProxy = new ManagerProxy(); + mListener.onProviderBound(); + + // Send the current request to the remote. + trySendCurrentRequest(); } } @@ -137,6 +137,8 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { + // Clear the ManagerProxy used with the old remote so we will ignore calls from any old + // remotes that somehow hold a reference to it. mManagerProxy = null; mListener.onProviderUnbound(); } @@ -150,23 +152,43 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { synchronized (mSharedLock) { mRequest = request; + // Two possible outcomes here: Either we are already bound to a remote service, in + // which case trySendCurrentRequest() will communicate the request immediately, or we + // are not bound to the remote service yet, in which case it will be sent during + // onBindOnHandlerThread() instead. trySendCurrentRequest(); } } @GuardedBy("mSharedLock") private void trySendCurrentRequest() { + ManagerProxy managerProxy = mManagerProxy; TimeZoneProviderRequest request = mRequest; mServiceWatcher.runOnBinder(binder -> { ITimeZoneProvider service = ITimeZoneProvider.Stub.asInterface(binder); if (request.sendUpdates()) { - service.startUpdates(request.getInitializationTimeout().toMillis()); + service.startUpdates(managerProxy, request.getInitializationTimeout().toMillis()); } else { service.stopUpdates(); } }); } + /** + * A stubbed implementation. + */ + @Override + void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) { + mThreadingDomain.assertCurrentThread(); + + if (callback != null) { + Bundle result = new Bundle(); + result.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false); + result.putString(TEST_COMMAND_RESULT_ERROR_KEY, "Not implemented"); + callback.sendResult(result); + } + } + @Override public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) { synchronized (mSharedLock) { @@ -205,6 +227,8 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { private void onTimeZoneProviderEvent(TimeZoneProviderEvent event) { synchronized (mSharedLock) { if (mManagerProxy != this) { + // Ignore incoming calls if this instance is no longer the current + // mManagerProxy. return; } } diff --git a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java deleted file mode 100644 index 0987ee5c17bc2..0000000000000 --- a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2020 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 com.android.server.location.timezone; - -import static com.android.server.location.timezone.LocationTimeZoneManagerService.PRIMARY_PROVIDER_NAME; -import static com.android.server.location.timezone.LocationTimeZoneManagerService.SECONDARY_PROVIDER_NAME; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.ShellCommand; -import android.os.SystemClock; -import android.service.timezone.TimeZoneProviderSuggestion; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -/** - * An event used for simulating real binder proxy behavior using a {@link - * SimulatedLocationTimeZoneProviderProxy}. - */ -final class SimulatedBinderProviderEvent { - - private static final List VALID_PROVIDER_NAMES = - Arrays.asList(PRIMARY_PROVIDER_NAME, SECONDARY_PROVIDER_NAME); - - static final int INJECTED_EVENT_TYPE_ON_BIND = 1; - static final int INJECTED_EVENT_TYPE_ON_UNBIND = 2; - static final int INJECTED_EVENT_TYPE_LOCATION_TIME_ZONE_EVENT = 3; - - - @NonNull private final String mProviderName; - private final int mType; - @Nullable private final TimeZoneProviderEvent mTimeZoneProviderEvent; - - private SimulatedBinderProviderEvent(@NonNull String providerName, int eventType, - @Nullable TimeZoneProviderEvent timeZoneProviderEvent) { - this.mProviderName = Objects.requireNonNull(providerName); - this.mType = eventType; - this.mTimeZoneProviderEvent = timeZoneProviderEvent; - } - - @NonNull - String getProviderName() { - return mProviderName; - } - - @Nullable - TimeZoneProviderEvent getTimeZoneProviderEvent() { - return mTimeZoneProviderEvent; - } - - int getType() { - return mType; - } - - /** Prints the command line options that {@link #createFromArgs(ShellCommand)} understands. */ - static void printCommandLineOpts(PrintWriter pw) { - pw.println("Simulated provider binder event:"); - pw.println(); - pw.println(" [onBind|onUnbind|timeZoneProviderEvent" - + " ]"); - pw.println(); - pw.println(" = " + VALID_PROVIDER_NAMES); - pw.println("