Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 95126bce authored by Neil Fuller's avatar Neil Fuller
Browse files

Initial version of LocationTimeZoneManagerService

This includes an initial version of LocationTimeZoneManagerService,
supporting / implementation classes and test infra.

This commit is not final code (see various TODOs) but forms the
scaffolding for later commits. The ControllerImpl only supports a single
LocationTimeZoneProvider, and the "real" binder implementation is left
for a later commit.

Test: atest services/tests/servicestests/src/com/android/server/location/timezone/
Bug: 149014708
Change-Id: Icfba9a816e55a51ee555e08bb889848644539735
parent d5283aff
Loading
Loading
Loading
Loading
+214 −0
Original line number Diff line number Diff line
/*
 * 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.debugLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.timezone.LocationTimeZoneEvent;
import android.util.IndentingPrintWriter;
import android.util.Slog;

import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;

import java.util.Objects;

/**
 * The real, system-server side implementation of a binder call backed {@link
 * LocationTimeZoneProvider}. It handles keeping track of current state, timeouts and ensuring
 * events are passed to the {@link LocationTimeZoneProviderController} on the required thread.
 */
class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {

    private static final String TAG = LocationTimeZoneManagerService.TAG;

    @NonNull private final LocationTimeZoneProviderProxy mProxy;

    BinderLocationTimeZoneProvider(
            @NonNull ThreadingDomain threadingDomain,
            @NonNull String providerName,
            @NonNull LocationTimeZoneProviderProxy proxy) {
        super(threadingDomain, providerName);
        mProxy = Objects.requireNonNull(proxy);
    }

    @Override
    void onInitialize() {
        mProxy.setListener(new LocationTimeZoneProviderProxy.Listener() {
            @Override
            public void onReportLocationTimeZoneEvent(
                    @NonNull LocationTimeZoneEvent locationTimeZoneEvent) {
                handleLocationTimeZoneEvent(locationTimeZoneEvent);
            }

            @Override
            public void onProviderBound() {
                handleOnProviderBound();
            }

            @Override
            public void onProviderUnbound() {
                handleProviderLost("onProviderUnbound()");
            }
        });
    }

    private void handleProviderLost(String reason) {
        mThreadingDomain.assertCurrentThread();

        synchronized (mSharedLock) {
            ProviderState currentState = mCurrentState.get();
            switch (currentState.stateEnum) {
                case PROVIDER_STATE_ENABLED: {
                    // Losing a remote provider is treated as becoming uncertain.
                    String msg = "handleProviderLost reason=" + reason
                            + ", mProviderName=" + mProviderName
                            + ", currentState=" + currentState;
                    debugLog(msg);
                    // This is an unusual PROVIDER_STATE_ENABLED state because event == null
                    ProviderState newState = currentState.newState(
                            PROVIDER_STATE_ENABLED, null, currentState.currentUserConfiguration,
                            msg);
                    setCurrentState(newState, true);
                    break;
                }
                case PROVIDER_STATE_DISABLED: {
                    debugLog("handleProviderLost reason=" + reason
                            + ", mProviderName=" + mProviderName
                            + ", currentState=" + currentState
                            + ": No state change required, provider is disabled.");
                    break;
                }
                case PROVIDER_STATE_PERM_FAILED: {
                    debugLog("handleProviderLost reason=" + reason
                            + ", mProviderName=" + mProviderName
                            + ", currentState=" + currentState
                            + ": No state change required, provider is perm failed.");
                    break;
                }
                default: {
                    throw new IllegalStateException("Unknown currentState=" + currentState);
                }
            }
        }
    }

    private void handleOnProviderBound() {
        mThreadingDomain.assertCurrentThread();

        synchronized (mSharedLock) {
            ProviderState currentState = mCurrentState.get();
            switch (currentState.stateEnum) {
                case PROVIDER_STATE_ENABLED: {
                    debugLog("handleOnProviderBound mProviderName=" + mProviderName
                            + ", currentState=" + currentState + ": Provider is enabled.");
                    break;
                }
                case PROVIDER_STATE_DISABLED: {
                    debugLog("handleOnProviderBound mProviderName=" + mProviderName
                            + ", currentState=" + currentState + ": Provider is disabled.");
                    break;
                }
                case PROVIDER_STATE_PERM_FAILED: {
                    debugLog("handleOnProviderBound"
                            + ", mProviderName=" + mProviderName
                            + ", currentState=" + currentState
                            + ": No state change required, provider is perm failed.");
                    break;
                }
                default: {
                    throw new IllegalStateException("Unknown currentState=" + currentState);
                }
            }
        }
    }

    @Override
    void onEnable() {
        // Set a request on the proxy - it will be sent immediately if the service is bound,
        // or will be sent as soon as the service becomes bound.
        // TODO(b/152744911): Decide whether to send a timeout so the provider knows how long
        //  it has to generate the first event before it could be bypassed.
        LocationTimeZoneProviderRequest request =
                new LocationTimeZoneProviderRequest.Builder()
                        .setReportLocationTimeZone(true)
                        .build();
        mProxy.setRequest(request);
    }

    @Override
    void onDisable() {
        LocationTimeZoneProviderRequest request =
                new LocationTimeZoneProviderRequest.Builder()
                        .setReportLocationTimeZone(false)
                        .build();
        mProxy.setRequest(request);
    }

    @Override
    void logWarn(String msg) {
        Slog.w(TAG, msg);
    }

    @Override
    public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
        synchronized (mSharedLock) {
            ipw.println("{BinderLocationTimeZoneProvider}");
            ipw.println("mProviderName=" + mProviderName);
            ipw.println("mCurrentState=" + mCurrentState);
            ipw.println("mProxy=" + mProxy);

            ipw.println("State history:");
            ipw.increaseIndent();
            mCurrentState.dump(ipw);
            ipw.decreaseIndent();

            ipw.println("Proxy details:");
            ipw.increaseIndent();
            mProxy.dump(ipw, args);
            ipw.decreaseIndent();
        }
    }

    @Override
    public String toString() {
        synchronized (mSharedLock) {
            return "BinderLocationTimeZoneProvider{"
                    + "mProviderName=" + mProviderName
                    + "mCurrentState=" + mCurrentState
                    + "mProxy=" + mProxy
                    + '}';
        }
    }

    /**
     * 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) {
        if (!(mProxy instanceof SimulatedLocationTimeZoneProviderProxy)) {
            Slog.w(TAG, mProxy + " is not a " + SimulatedLocationTimeZoneProviderProxy.class
                    + ", event=" + event);
            return;
        }
        ((SimulatedLocationTimeZoneProviderProxy) mProxy).simulate(event);
    }
}
+43 −0
Original line number Diff line number Diff line
/*
 * 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 android.annotation.NonNull;

import com.android.server.LocalServices;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;

/**
 * The real implementation of {@link LocationTimeZoneProviderController.Callback} used by
 * {@link ControllerImpl} to interact with other server components.
 */
class ControllerCallbackImpl extends LocationTimeZoneProviderController.Callback {

    ControllerCallbackImpl(@NonNull ThreadingDomain threadingDomain) {
        super(threadingDomain);
    }

    @Override
    void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion) {
        mThreadingDomain.assertCurrentThread();

        TimeZoneDetectorInternal timeZoneDetector =
                LocalServices.getService(TimeZoneDetectorInternal.class);
        timeZoneDetector.suggestGeolocationTimeZone(suggestion);
    }
}
+51 −0
Original line number Diff line number Diff line
/*
 * 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 android.annotation.NonNull;

import com.android.server.LocalServices;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;

import java.util.Objects;

/**
 * The real implementation of {@link LocationTimeZoneProviderController.Environment} used by
 * {@link ControllerImpl} to interact with other server components.
 */
class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {

    @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
    @NonNull private final LocationTimeZoneProviderController mController;

    ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
            @NonNull LocationTimeZoneProviderController controller) {
        super(threadingDomain);
        mController = Objects.requireNonNull(controller);
        mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class);

        // Listen for configuration changes.
        mTimeZoneDetectorInternal.addConfigurationListener(
                () -> mThreadingDomain.post(mController::onConfigChanged));
    }

    @Override
    ConfigurationInternal getCurrentUserConfigurationInternal() {
        return mTimeZoneDetectorInternal.getCurrentUserConfigurationInternal();
    }
}
+420 −0

File added.

Preview size limit exceeded, changes collapsed.

+73 −0
Original line number Diff line number Diff line
/*
 * 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 android.annotation.NonNull;
import android.os.Handler;

import java.util.Objects;

/**
 * The real implementation of {@link ThreadingDomain} that uses a {@link Handler}.
 */
final class HandlerThreadingDomain extends ThreadingDomain {

    @NonNull private final Handler mHandler;

    HandlerThreadingDomain(Handler handler) {
        mHandler = Objects.requireNonNull(handler);
    }

    /**
     * Returns the {@link Handler} associated with this threading domain. The same {@link Handler}
     * may be associated with multiple threading domains, e.g. multiple threading domains could
     * choose to use the {@link com.android.server.FgThread} handler.
     *
     * <p>If you find yourself making this public because you need a {@link Handler}, then it may
     * cause problems with testability. Try to avoid using this method and use methods like {@link
     * #post(Runnable)} instead.
     */
    @NonNull
    Handler getHandler() {
        return mHandler;
    }

    @NonNull
    Thread getThread() {
        return getHandler().getLooper().getThread();
    }

    @Override
    void post(@NonNull Runnable r) {
        getHandler().post(r);
    }

    @Override
    void postDelayed(@NonNull Runnable r, long delayMillis) {
        getHandler().postDelayed(r, delayMillis);
    }

    @Override
    void postDelayed(Runnable r, Object token, long delayMillis) {
        getHandler().postDelayed(r, token, delayMillis);
    }

    @Override
    void removeQueuedRunnables(Object token) {
        getHandler().removeCallbacksAndMessages(token);
    }
}
Loading