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

Commit f448a290 authored by Neil Fuller's avatar Neil Fuller Committed by Android (Google) Code Review
Browse files

Merge "Implementation of time user configuration update"

parents a7003460 7573cc80
Loading
Loading
Loading
Loading
+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
+4 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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);
+16 −16
Original line number Diff line number Diff line
@@ -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)
@@ -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);
}
+133 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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 {
@@ -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,
@@ -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
@@ -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
+4 −3
Original line number Diff line number Diff line
@@ -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();

@@ -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