blob: aa72722c53ae81408cb336cb949f8f69d9e1ed07 [file] [log] [blame]
/*
* Copyright 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 com.android.internal.telephony.nitz;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.TimestampedValue;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.NitzData;
import com.android.internal.telephony.NitzStateMachine.DeviceState;
import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
import com.android.telephony.Rlog;
import java.util.Arrays;
import java.util.Objects;
/**
* A factory class for the {@link NitzSignalInputFilterPredicate} instance used by
* {@link NitzStateMachineImpl}. This class is exposed for testing and provides access to various
* internal components.
*/
@VisibleForTesting
public final class NitzSignalInputFilterPredicateFactory {
private static final String LOG_TAG = NitzStateMachineImpl.LOG_TAG;
private static final boolean DBG = NitzStateMachineImpl.DBG;
private static final String WAKELOCK_TAG = "NitzSignalInputFilterPredicateFactory";
private NitzSignalInputFilterPredicateFactory() {}
/**
* Returns the real {@link NitzSignalInputFilterPredicate} to use for NITZ signal input
* filtering.
*/
@NonNull
public static NitzSignalInputFilterPredicate create(
@NonNull Context context, @NonNull DeviceState deviceState) {
Objects.requireNonNull(context);
Objects.requireNonNull(deviceState);
TrivalentPredicate[] components = new TrivalentPredicate[] {
// Disables NITZ processing entirely: can return false or null.
createIgnoreNitzPropertyCheck(deviceState),
// Filters bad reference times from new signals: can return false or null.
createBogusElapsedRealtimeCheck(context, deviceState),
// Ensures oldSignal == null is always processed: can return true or null.
createNoOldSignalCheck(),
// Adds rate limiting: can return true or false.
createRateLimitCheck(deviceState),
};
return new NitzSignalInputFilterPredicateImpl(components);
}
/**
* A filtering function that can give a {@code true} (must process), {@code false} (must not
* process) and a {@code null} (no opinion) response given a previous NITZ signal and a new
* signal. The previous signal may be {@code null} (unless ruled out by a prior
* {@link TrivalentPredicate}).
*/
@VisibleForTesting
@FunctionalInterface
public interface TrivalentPredicate {
/**
* See {@link TrivalentPredicate}.
*/
@Nullable
Boolean mustProcessNitzSignal(
@Nullable TimestampedValue<NitzData> previousSignal,
@NonNull TimestampedValue<NitzData> newSignal);
}
/**
* Returns a {@link TrivalentPredicate} function that implements a check for the
* "gsm.ignore-nitz" Android system property. The function can return {@code false} or
* {@code null}.
*/
@VisibleForTesting
@NonNull
public static TrivalentPredicate createIgnoreNitzPropertyCheck(
@NonNull DeviceState deviceState) {
return (oldSignal, newSignal) -> {
boolean ignoreNitz = deviceState.getIgnoreNitz();
if (ignoreNitz) {
if (DBG) {
Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal because"
+ " gsm.ignore-nitz is set");
}
return false;
}
return null;
};
}
/**
* Returns a {@link TrivalentPredicate} function that implements a check for a bad reference
* time associated with {@code newSignal}. The function can return {@code false} or
* {@code null}.
*/
@VisibleForTesting
@NonNull
public static TrivalentPredicate createBogusElapsedRealtimeCheck(
@NonNull Context context, @NonNull DeviceState deviceState) {
PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
final WakeLock wakeLock =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
return (oldSignal, newSignal) -> {
Objects.requireNonNull(newSignal);
// Validate the newSignal to reject obviously bogus elapsedRealtime values.
try {
// Acquire the wake lock as we are reading the elapsed realtime clock below.
wakeLock.acquire();
long elapsedRealtime = deviceState.elapsedRealtime();
long millisSinceNitzReceived = elapsedRealtime - newSignal.getReferenceTimeMillis();
if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
if (DBG) {
Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal"
+ " because unexpected elapsedRealtime=" + elapsedRealtime
+ " nitzSignal=" + newSignal);
}
return false;
}
return null;
} finally {
wakeLock.release();
}
};
}
/**
* Returns a {@link TrivalentPredicate} function that implements a check for a {@code null}
* {@code oldSignal} (indicating there's no history). The function can return {@code true}
* or {@code null}.
*/
@VisibleForTesting
@NonNull
public static TrivalentPredicate createNoOldSignalCheck() {
// Always process a signal when there was no previous signal.
return (oldSignal, newSignal) -> oldSignal == null ? true : null;
}
/**
* Returns a {@link TrivalentPredicate} function that implements filtering using
* {@code oldSignal} and {@code newSignal}. The function can return {@code true} or
* {@code false} and so is intended as the final function in a chain.
*
* Function detail: if an NITZ signal received that is too similar to a previous one
* it should be disregarded if it's received within a configured time period.
* The general contract for {@link TrivalentPredicate} allows {@code previousSignal} to be
* {@code null}, but previous functions are expected to prevent it in this case.
*/
@VisibleForTesting
@NonNull
public static TrivalentPredicate createRateLimitCheck(@NonNull DeviceState deviceState) {
return new TrivalentPredicate() {
@Override
@NonNull
public Boolean mustProcessNitzSignal(
@NonNull TimestampedValue<NitzData> previousSignal,
@NonNull TimestampedValue<NitzData> newSignal) {
Objects.requireNonNull(newSignal);
Objects.requireNonNull(newSignal.getValue());
Objects.requireNonNull(previousSignal);
Objects.requireNonNull(previousSignal.getValue());
NitzData newNitzData = newSignal.getValue();
NitzData previousNitzData = previousSignal.getValue();
// Compare the discrete NitzData fields associated with local time offset. Any
// difference and we should process the signal regardless of how recent the last one
// was.
if (!offsetInfoIsTheSame(previousNitzData, newNitzData)) {
return true;
}
// Now check the continuous NitzData field (time) to see if it is sufficiently
// different.
int nitzUpdateSpacing = deviceState.getNitzUpdateSpacingMillis();
int nitzUpdateDiff = deviceState.getNitzUpdateDiffMillis();
// Calculate the elapsed time between the new signal and the last signal.
long elapsedRealtimeSinceLastSaved = newSignal.getReferenceTimeMillis()
- previousSignal.getReferenceTimeMillis();
// Calculate the UTC difference between the time the two signals hold.
long utcTimeDifferenceMillis = newNitzData.getCurrentTimeInMillis()
- previousNitzData.getCurrentTimeInMillis();
// Ideally the difference between elapsedRealtimeSinceLastSaved and
// utcTimeDifferenceMillis would be zero.
long millisGainedOrLost = Math
.abs(utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved);
if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
|| millisGainedOrLost > nitzUpdateDiff) {
return true;
}
if (DBG) {
Rlog.d(LOG_TAG, "mustProcessNitzSignal: NITZ signal filtered"
+ " previousSignal=" + previousSignal
+ ", newSignal=" + newSignal
+ ", nitzUpdateSpacing=" + nitzUpdateSpacing
+ ", nitzUpdateDiff=" + nitzUpdateDiff);
}
return false;
}
private boolean offsetInfoIsTheSame(NitzData one, NitzData two) {
return Objects.equals(two.getDstAdjustmentMillis(), one.getDstAdjustmentMillis())
&& Objects.equals(
two.getEmulatorHostTimeZone(), one.getEmulatorHostTimeZone())
&& two.getLocalOffsetMillis() == one.getLocalOffsetMillis();
}
};
}
/**
* An implementation of {@link NitzSignalInputFilterPredicate} that tries a series of
* {@link TrivalentPredicate} instances until one provides a {@code true} or {@code false}
* response indicating that the {@code newSignal} should be processed or not. If all return
* {@code null} then a default of {@code true} is returned.
*/
@VisibleForTesting
public static class NitzSignalInputFilterPredicateImpl
implements NitzSignalInputFilterPredicate {
@NonNull
private final TrivalentPredicate[] mComponents;
@VisibleForTesting
public NitzSignalInputFilterPredicateImpl(@NonNull TrivalentPredicate[] components) {
this.mComponents = Arrays.copyOf(components, components.length);
}
@Override
public boolean mustProcessNitzSignal(@Nullable TimestampedValue<NitzData> oldSignal,
@NonNull TimestampedValue<NitzData> newSignal) {
Objects.requireNonNull(newSignal);
for (TrivalentPredicate component : mComponents) {
Boolean result = component.mustProcessNitzSignal(oldSignal, newSignal);
if (result != null) {
return result;
}
}
// The default is to process.
return true;
}
}
}