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

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

Add a new method to set time

Before this change there are a various components that set the system
clock by directly calling AlarmManager.setTime(). This change exposes a
new method on TimeDetector to use when setting the device time manually
(e.g. via settings) and modifies some callers to use it.

The intent is to later restrict the number of distinct processes that
can manipulate the device system clock directly so that all time changes
go through the time detector service, which can enforce policy, log
the reasons for changes, and so on.

Bug: 140712361
Test: atest com.android.server.timedetector
Change-Id: I9300dba868ed61249d0848b0dd4b953996161bda
parent 0715e47f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app.timedetector;

import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;

/**
@@ -33,4 +34,5 @@ import android.app.timedetector.PhoneTimeSuggestion;
 */
interface ITimeDetectorService {
  void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion);
  void suggestManualTime(in ManualTimeSuggestion timeSuggestion);
}
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.timedetector;

parcelable ManualTimeSuggestion;
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.timedetector;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.TimestampedValue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * A time signal from a manual (user provided) source. The value consists of the number of
 * milliseconds elapsed since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime
 * clock when that number was established. The elapsed realtime clock is considered accurate but
 * volatile, so time signals must not be persisted across device resets.
 *
 * @hide
 */
public final class ManualTimeSuggestion implements Parcelable {

    public static final @NonNull Creator<ManualTimeSuggestion> CREATOR =
            new Creator<ManualTimeSuggestion>() {
                public ManualTimeSuggestion createFromParcel(Parcel in) {
                    return ManualTimeSuggestion.createFromParcel(in);
                }

                public ManualTimeSuggestion[] newArray(int size) {
                    return new ManualTimeSuggestion[size];
                }
            };

    @NonNull
    private final TimestampedValue<Long> mUtcTime;
    @Nullable
    private ArrayList<String> mDebugInfo;

    public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
        mUtcTime = Objects.requireNonNull(utcTime);
    }

    private static ManualTimeSuggestion createFromParcel(Parcel in) {
        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
        ManualTimeSuggestion suggestion = new ManualTimeSuggestion(utcTime);
        @SuppressWarnings("unchecked")
        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
        suggestion.mDebugInfo = debugInfo;
        return suggestion;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeParcelable(mUtcTime, 0);
        dest.writeList(mDebugInfo);
    }

    @NonNull
    public TimestampedValue<Long> getUtcTime() {
        return mUtcTime;
    }

    @NonNull
    public List<String> getDebugInfo() {
        return Collections.unmodifiableList(mDebugInfo);
    }

    /**
     * Associates information with the instance that can be useful for debugging / logging. The
     * information is present in {@link #toString()} but is not considered for
     * {@link #equals(Object)} and {@link #hashCode()}.
     */
    public void addDebugInfo(String... debugInfos) {
        if (mDebugInfo == null) {
            mDebugInfo = new ArrayList<>();
        }
        mDebugInfo.addAll(Arrays.asList(debugInfos));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        ManualTimeSuggestion that = (ManualTimeSuggestion) o;
        return Objects.equals(mUtcTime, that.mUtcTime);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mUtcTime);
    }

    @Override
    public String toString() {
        return "ManualTimeSuggestion{"
                + "mUtcTime=" + mUtcTime
                + ", mDebugInfo=" + mDebugInfo
                + '}';
    }
}
+33 −4
Original line number Diff line number Diff line
@@ -17,19 +17,22 @@
package android.app.timedetector;

import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemClock;
import android.util.Log;
import android.util.TimestampedValue;

/**
 * The interface through which system components can send signals to the TimeDetectorService.
 * @hide
 */
@SystemService(Context.TIME_DETECTOR_SERVICE)
public final class TimeDetector {
public class TimeDetector {
    private static final String TAG = "timedetector.TimeDetector";
    private static final boolean DEBUG = false;

@@ -41,10 +44,11 @@ public final class TimeDetector {
    }

    /**
     * Suggests the current time to the detector. The detector may ignore the signal if better
     * signals are available such as those that come from more reliable sources or were
     * determined more recently.
     * Suggests the current phone-signal derived time to the detector. The detector may ignore the
     * signal if better signals are available such as those that come from more reliable sources or
     * were determined more recently.
     */
    @RequiresPermission(android.Manifest.permission.SET_TIME)
    public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
        if (DEBUG) {
            Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion);
@@ -56,4 +60,29 @@ public final class TimeDetector {
        }
    }

    /**
     * Suggests the user's manually entered current time to the detector.
     */
    @RequiresPermission(android.Manifest.permission.SET_TIME)
    public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) {
        if (DEBUG) {
            Log.d(TAG, "suggestManualTime called: " + timeSuggestion);
        }
        try {
            mITimeDetectorService.suggestManualTime(timeSuggestion);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * A shared utility method to create a {@link ManualTimeSuggestion}.
     */
    public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) {
        TimestampedValue<Long> utcTime =
                new TimestampedValue<>(SystemClock.elapsedRealtime(), when);
        ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(utcTime);
        manualTimeSuggestion.addDebugInfo(why);
        return manualTimeSuggestion;
    }
}
+85 −46
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@

package com.android.server.timedetector;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent;
import android.util.Slog;
@@ -27,6 +29,8 @@ import android.util.TimestampedValue;
import com.android.internal.telephony.TelephonyIntents;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
@@ -38,10 +42,22 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {

    private final static String TAG = "timedetector.SimpleTimeDetectorStrategy";

    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Origin {}

    /** Used when a time value originated from a telephony signal. */
    @Origin
    private static final int ORIGIN_PHONE = 1;

    /** Used when a time value originated from a user / manual settings. */
    @Origin
    private static final int ORIGIN_MANUAL = 2;

    /**
     * CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the
     * actual system clock time before a warning is logged. Used to help identify situations where
     * there is something other than this class setting the system clock.
     * there is something other than this class setting the system clock automatically.
     */
    private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;

@@ -52,11 +68,11 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
    @Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;

    // Information about the last time signal received: Used when toggling auto-time.
    @Nullable private TimestampedValue<Long> mLastSystemClockTime;
    private boolean mLastSystemClockTimeSendNetworkBroadcast;
    @Nullable private TimestampedValue<Long> mLastAutoSystemClockTime;
    private boolean mLastAutoSystemClockTimeSendNetworkBroadcast;

    // System clock state.
    @Nullable private TimestampedValue<Long> mLastSystemClockTimeSet;
    @Nullable private TimestampedValue<Long> mLastAutoSystemClockTimeSet;

    @Override
    public void initialize(@NonNull Callback callback) {
@@ -78,17 +94,18 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
            return;
        }
        // Always store the last NITZ value received, regardless of whether we go on to use it to
        // update the system clock. This is so that we can validate future NITZ signals.
        // update the system clock. This is so that we can validate future phone suggestions.
        mLastPhoneSuggestion = timeSuggestion;

        // System clock update logic.
        final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
        setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, timeSuggestion);
    }

        // Historically, Android has sent a telephony broadcast only when setting the time using
        // NITZ.
        final boolean sendNetworkBroadcast = true;

    @Override
    public void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
        final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
        setSystemClockIfRequired(newUtcTime, sendNetworkBroadcast);
        setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion);
    }

    private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
@@ -110,54 +127,76 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
    }

    private void setSystemClockIfRequired(
            TimestampedValue<Long> time, boolean sendNetworkBroadcast) {

        // Store the last candidate we've seen in all cases so we can set the system clock
        // when/if time detection is enabled.
        mLastSystemClockTime = time;
        mLastSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;

        if (!mCallback.isTimeDetectionEnabled()) {
            Slog.d(TAG, "setSystemClockIfRequired: Time detection is not enabled. time=" + time);
            @Origin int origin, TimestampedValue<Long> time, Object cause) {
        // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
        // when setting the time using NITZ.
        boolean sendNetworkBroadcast = origin == ORIGIN_PHONE;

        boolean isOriginAutomatic = isOriginAutomatic(origin);
        if (isOriginAutomatic) {
            // Store the last auto time candidate we've seen in all cases so we can set the system
            // clock when/if time detection is off but later enabled.
            mLastAutoSystemClockTime = time;
            mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;

            if (!mCallback.isAutoTimeDetectionEnabled()) {
                Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is not enabled."
                        + " time=" + time
                        + ", cause=" + cause);
                return;
            }
        } else {
            if (mCallback.isAutoTimeDetectionEnabled()) {
                Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is enabled."
                        + " time=" + time
                        + ", cause=" + cause);
                return;
            }
        }

        mCallback.acquireWakeLock();
        try {
            long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
            long actualTimeMillis = mCallback.systemClockMillis();

            if (isOriginAutomatic) {
                // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
                // may be setting the clock.
            if (mLastSystemClockTimeSet != null) {
                if (mLastAutoSystemClockTimeSet != null) {
                    long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
                        mLastSystemClockTimeSet, elapsedRealtimeMillis);
                            mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
                    long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis);
                    if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
                    Slog.w(TAG, "System clock has not tracked elapsed real time clock. A clock may"
                            + " be inaccurate or something unexpectedly set the system clock."
                        Slog.w(TAG,
                                "System clock has not tracked elapsed real time clock. A clock may"
                                        + " be inaccurate or something unexpectedly set the system"
                                        + " clock."
                                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                                        + " expectedTimeMillis=" + expectedTimeMillis
                                        + " actualTimeMillis=" + actualTimeMillis);
                    }
                }
            }

            final String reason = "New time signal";
            adjustAndSetDeviceSystemClock(
                    time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, reason);
                    time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, cause);
        } finally {
            mCallback.releaseWakeLock();
        }
    }

    private static boolean isOriginAutomatic(@Origin int origin) {
        return origin == ORIGIN_PHONE;
    }

    @Override
    public void handleAutoTimeDetectionToggle(boolean enabled) {
        // If automatic time detection is enabled we update the system clock instantly if we can.
        // Conversely, if automatic time detection is disabled we leave the clock as it is.
        if (enabled) {
            if (mLastSystemClockTime != null) {
            if (mLastAutoSystemClockTime != null) {
                // Only send the network broadcast if the last candidate would have caused one.
                final boolean sendNetworkBroadcast = mLastSystemClockTimeSendNetworkBroadcast;
                final boolean sendNetworkBroadcast = mLastAutoSystemClockTimeSendNetworkBroadcast;

                mCallback.acquireWakeLock();
                try {
@@ -165,7 +204,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
                    long actualTimeMillis = mCallback.systemClockMillis();

                    final String reason = "Automatic time detection enabled.";
                    adjustAndSetDeviceSystemClock(mLastSystemClockTime, sendNetworkBroadcast,
                    adjustAndSetDeviceSystemClock(mLastAutoSystemClockTime, sendNetworkBroadcast,
                            elapsedRealtimeMillis, actualTimeMillis, reason);
                } finally {
                    mCallback.releaseWakeLock();
@@ -174,22 +213,22 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
        } else {
            // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
            // it should be in future.
            mLastSystemClockTimeSet = null;
            mLastAutoSystemClockTimeSet = null;
        }
    }

    @Override
    public void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
        pw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion);
        pw.println("mLastSystemClockTimeSet=" + mLastSystemClockTimeSet);
        pw.println("mLastSystemClockTime=" + mLastSystemClockTime);
        pw.println("mLastSystemClockTimeSendNetworkBroadcast="
                + mLastSystemClockTimeSendNetworkBroadcast);
        pw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
        pw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime);
        pw.println("mLastAutoSystemClockTimeSendNetworkBroadcast="
                + mLastAutoSystemClockTimeSendNetworkBroadcast);
    }

    private void adjustAndSetDeviceSystemClock(
            TimestampedValue<Long> newTime, boolean sendNetworkBroadcast,
            long elapsedRealtimeMillis, long actualSystemClockMillis, String reason) {
            long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) {

        // Adjust for the time that has elapsed since the signal was received.
        long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
@@ -203,20 +242,20 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
                    + " system clock are close enough."
                    + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                    + " newTime=" + newTime
                    + " reason=" + reason
                    + " cause=" + cause
                    + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
                    + " absTimeDifference=" + absTimeDifference);
            return;
        }

        Slog.d(TAG, "Setting system clock using time=" + newTime
                + " reason=" + reason
                + " cause=" + cause
                + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                + " newTimeMillis=" + newSystemClockMillis);
        mCallback.setSystemClock(newSystemClockMillis);

        // CLOCK_PARANOIA : Record the last time this class set the system clock.
        mLastSystemClockTimeSet = newTime;
        mLastAutoSystemClockTimeSet = newTime;

        if (sendNetworkBroadcast) {
            // Send a broadcast that telephony code used to send after setting the clock.
Loading