blob: b5ac712a3522985b4611af9a0d1972dd6cb99f27 [file] [log] [blame]
/*
* 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.timezonedetector.location;
import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.service.timezone.ITimeZoneProvider;
import android.service.timezone.ITimeZoneProviderManager;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
/**
* System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the
* system server object used to communicate with a remote {@link
* android.service.timezone.TimeZoneProviderService} over Binder, which could be running in a
* different process. As "remote" providers are bound / unbound this proxy will rebind to the "best"
* available remote process.
*/
class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy implements
ServiceListener<BoundServiceInfo> {
@NonNull private final ServiceWatcher mServiceWatcher;
@GuardedBy("mSharedLock")
@Nullable private ManagerProxy mManagerProxy;
@GuardedBy("mSharedLock")
@NonNull private TimeZoneProviderRequest mRequest;
RealLocationTimeZoneProviderProxy(
@NonNull Context context, @NonNull Handler handler,
@NonNull ThreadingDomain threadingDomain, @NonNull String action,
@NonNull String providerPackageName) {
super(context, threadingDomain);
mManagerProxy = null;
mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
Objects.requireNonNull(providerPackageName);
mServiceWatcher = ServiceWatcher.create(context,
handler,
"RealLocationTimeZoneProviderProxy",
new CurrentUserServiceSupplier(context, action,
providerPackageName, BIND_TIME_ZONE_PROVIDER_SERVICE,
INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE),
this);
}
@Override
void onInitialize() {
if (!register()) {
throw new IllegalStateException("Unable to register binder proxy");
}
}
@Override
void onDestroy() {
mServiceWatcher.unregister();
}
private boolean register() {
boolean resolves = mServiceWatcher.checkServiceResolves();
if (resolves) {
mServiceWatcher.register();
}
return resolves;
}
@Override
public void onBind(IBinder binder, BoundServiceInfo boundService) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
// 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();
}
}
@Override
public void onUnbind() {
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();
}
}
@Override
final void setRequest(@NonNull TimeZoneProviderRequest request) {
mThreadingDomain.assertCurrentThread();
Objects.requireNonNull(request);
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(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) {
ipw.println("{RealLocationTimeZoneProviderProxy}");
ipw.println("mRequest=" + mRequest);
mServiceWatcher.dump(ipw);
}
}
/**
* A system Server-side proxy for the ITimeZoneProviderManager, i.e. this is a local binder stub
* Each "remote" TimeZoneProvider is passed a binder instance that it then uses to communicate
* back with the system server, invoking the logic here.
*/
private class ManagerProxy extends ITimeZoneProviderManager.Stub {
// executed on binder thread
@Override
public void onTimeZoneProviderSuggestion(TimeZoneProviderSuggestion suggestion) {
onTimeZoneProviderEvent(TimeZoneProviderEvent.createSuggestionEvent(suggestion));
}
// executed on binder thread
@Override
public void onTimeZoneProviderUncertain() {
onTimeZoneProviderEvent(TimeZoneProviderEvent.createUncertainEvent());
}
// executed on binder thread
@Override
public void onTimeZoneProviderPermanentFailure(String failureReason) {
onTimeZoneProviderEvent(
TimeZoneProviderEvent.createPermanentFailureEvent(failureReason));
}
private void onTimeZoneProviderEvent(TimeZoneProviderEvent event) {
synchronized (mSharedLock) {
if (mManagerProxy != this) {
// Ignore incoming calls if this instance is no longer the current
// mManagerProxy.
return;
}
}
handleTimeZoneProviderEvent(event);
}
}
}