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

Commit b2837a16 authored by Neil Fuller's avatar Neil Fuller
Browse files

Enable providers to report they are "missing"

Previously, providers were only expected to report suggestions, not
status / events. This meant that "missing" providers could pretend to be
"real" until they were asked for a suggestion at which point they would
"fail".

However, now the LocationTimeZoneProviderController tracks provider
status. It's useful (and clearer) to know if providers are present even
if they haven't been asked to do something yet. In order to determine
the difference between a provider that hasn't been started yet and a
provider that is missing, this commit modifies
LocationTimeZoneProvider's initialize() methods to return a boolean
that can indicating initalization has failed. It could already throw a
RuntimeException for the same result, but returning a boolean is a
explicit interface, leaving the RuntimeException path still to handle
coding errors and other exceptional failures.

There is a new "DisabledLocationTimeZoneProvider" to cover the "there is
no provider" case at the LocationTimeZoneProvider level which is
hardcoded to return false from initialize.

This change means that the NullLocationTimeZoneProviderProxy, which
previously handled the "there is no provider" case at a deeper level,
can be deleted.

Bug: 236624675
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/location/
Change-Id: I7841f91e7ced4b59be27592e2e7c0e3d9c3acf7a
parent a4aee0d5
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -53,7 +53,7 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
    }

    @Override
    void onInitialize() {
    boolean onInitialize() {
        mProxy.initialize(new LocationTimeZoneProviderProxy.Listener() {
            @Override
            public void onReportTimeZoneProviderEvent(
@@ -71,6 +71,7 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
                handleTemporaryFailure("onProviderUnbound()");
            }
        });
        return true;
    }

    @Override
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 * 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.
@@ -18,57 +18,69 @@ package com.android.server.timezonedetector.location;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.SystemClock;
import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;

import java.time.Duration;

/**
 * A {@link LocationTimeZoneProviderProxy} that provides minimal responses needed for the {@link
 * BinderLocationTimeZoneProvider} to operate correctly when there is no "real" provider
 * configured / enabled. This can be used during development / testing, or in a production build
 * when the platform supports more providers than are needed for an Android deployment.
 * A {@link LocationTimeZoneProvider} that provides minimal responses needed to operate correctly
 * when there is no "real" provider configured / enabled. This is used when the platform supports
 * more providers than are needed for an Android deployment.
 *
 * <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
 * and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
 * config will be left null and the {@link LocationTimeZoneProviderProxy} implementation will be
 * defaulted to a {@link NullLocationTimeZoneProviderProxy}. The {@link
 * NullLocationTimeZoneProviderProxy} sends a "permanent failure" event immediately after being
 * started for the first time, which ensures the {@link LocationTimeZoneProviderController} won't
 * expect any further {@link TimeZoneProviderEvent}s to come from it, and won't attempt to use it
 * again.
 * <p>That is, the {@link LocationTimeZoneProviderController} supports a primary and a secondary
 * {@link LocationTimeZoneProvider}, but if only a primary is configured, the secondary provider
 * config will marked as "disabled" and the {@link LocationTimeZoneProvider} implementation will use
 * {@link DisabledLocationTimeZoneProvider}. The {@link DisabledLocationTimeZoneProvider} fails
 * initialization and immediately moves to a "permanent failure" state, which ensures the {@link
 * LocationTimeZoneProviderController} correctly categorizes it and won't attempt to use it.
 */
class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
class DisabledLocationTimeZoneProvider extends LocationTimeZoneProvider {

    /** Creates the instance. */
    NullLocationTimeZoneProviderProxy(
            @NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
        super(context, threadingDomain);
    DisabledLocationTimeZoneProvider(
            @NonNull ProviderMetricsLogger providerMetricsLogger,
            @NonNull ThreadingDomain threadingDomain,
            @NonNull String providerName,
            boolean recordStateChanges) {
        super(providerMetricsLogger, threadingDomain, providerName, x -> x, recordStateChanges);
    }

    @Override
    void onInitialize() {
        // No-op
    boolean onInitialize() {
        // Fail initialization, preventing further use.
        return false;
    }

    @Override
    void onDestroy() {
        // No-op
    }

    @Override
    void setRequest(@NonNull TimeZoneProviderRequest request) {
        if (request.sendUpdates()) {
            TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
                    SystemClock.elapsedRealtime(), "Provider is disabled");
            handleTimeZoneProviderEvent(event);
    void onStartUpdates(@NonNull Duration initializationTimeout,
            @NonNull Duration eventFilteringAgeThreshold) {
        throw new UnsupportedOperationException("Provider is disabled");
    }

    @Override
    void onStopUpdates() {
        throw new UnsupportedOperationException("Provider is disabled");
    }

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

    @Override
    public String toString() {
        synchronized (mSharedLock) {
            return "DisabledLocationTimeZoneProvider{"
                    + "mProviderName=" + mProviderName
                    + ", mCurrentState=" + mCurrentState
                    + '}';
        }
    }
}
+12 −16
Original line number Diff line number Diff line
@@ -447,12 +447,19 @@ public class LocationTimeZoneManagerService extends Binder {

        @NonNull
        LocationTimeZoneProvider createProvider() {
            LocationTimeZoneProviderProxy proxy = createProxy();
            ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex);

            String mode = getMode();
            if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
                return new DisabledLocationTimeZoneProvider(providerMetricsLogger, mThreadingDomain,
                        mName, mServiceConfigAccessor.getRecordStateChangesForTests());
            } else {
                LocationTimeZoneProviderProxy proxy = createBinderProxy();
                return new BinderLocationTimeZoneProvider(
                        providerMetricsLogger, mThreadingDomain, mName, proxy,
                        mServiceConfigAccessor.getRecordStateChangesForTests());
            }
        }

        @Override
        public void dump(IndentingPrintWriter ipw, String[] args) {
@@ -460,17 +467,6 @@ public class LocationTimeZoneManagerService extends Binder {
            ipw.printf("getPackageName()=%s\n", getPackageName());
        }

        @NonNull
        private LocationTimeZoneProviderProxy createProxy() {
            String mode = getMode();
            if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
                return new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
            } else {
                // mode == PROVIDER_MODE_OVERRIDE_ENABLED (or unknown).
                return createRealProxy();
            }
        }

        /** Returns the mode of the provider (enabled/disabled). */
        @NonNull
        private String getMode() {
@@ -482,7 +478,7 @@ public class LocationTimeZoneManagerService extends Binder {
        }

        @NonNull
        private RealLocationTimeZoneProviderProxy createRealProxy() {
        private RealLocationTimeZoneProviderProxy createBinderProxy() {
            String providerServiceAction = mServiceAction;
            boolean isTestProvider = isTestProvider();
            String providerPackageName = getPackageName();
+15 −4
Original line number Diff line number Diff line
@@ -448,13 +448,21 @@ abstract class LocationTimeZoneProvider implements Dumpable {
            currentState = currentState.newState(PROVIDER_STATE_STOPPED, null, null, "initialize");
            setCurrentState(currentState, false);

            boolean initializationSuccess;
            String initializationFailureReason;
            // Guard against uncaught exceptions due to initialization problems.
            try {
                onInitialize();
                initializationSuccess = onInitialize();
                initializationFailureReason = "onInitialize() returned false";
            } catch (RuntimeException e) {
                warnLog("Unable to initialize the provider", e);
                warnLog("Unable to initialize the provider due to exception", e);
                initializationSuccess = false;
                initializationFailureReason = "onInitialize() threw exception:" + e.getMessage();
            }

            if (!initializationSuccess) {
                currentState = currentState.newState(PROVIDER_STATE_PERM_FAILED, null, null,
                        "Failed to initialize: " + e.getMessage());
                        "Failed to initialize: " + initializationFailureReason);
                setCurrentState(currentState, true);
            }
        }
@@ -462,9 +470,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {

    /**
     * Implemented by subclasses to do work during {@link #initialize}.
     *
     * @return returns {@code true} on success, {@code false} if the provider should be considered
     *   "permanently failed" / disabled
     */
    @GuardedBy("mSharedLock")
    abstract void onInitialize();
    abstract boolean onInitialize();

    /**
     * Destroys the provider. Called after the provider is stopped. This instance will not be called
+2 −4
Original line number Diff line number Diff line
@@ -1672,11 +1672,9 @@ public class LocationTimeZoneProviderControllerTest {
        }

        @Override
        void onInitialize() {
        boolean onInitialize() {
            mInitialized = true;
            if (mFailDuringInitialization) {
                throw new RuntimeException("Simulated initialization failure");
            }
            return !mFailDuringInitialization;
        }

        @Override
Loading