1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.nitz;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.os.PowerManager;
23 import android.os.PowerManager.WakeLock;
24 import android.os.TimestampedValue;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.telephony.NitzData;
28 import com.android.internal.telephony.NitzStateMachine.DeviceState;
29 import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
30 import com.android.telephony.Rlog;
31 
32 import java.util.Arrays;
33 import java.util.Objects;
34 
35 /**
36  * A factory class for the {@link NitzSignalInputFilterPredicate} instance used by
37  * {@link NitzStateMachineImpl}. This class is exposed for testing and provides access to various
38  * internal components.
39  */
40 @VisibleForTesting
41 public final class NitzSignalInputFilterPredicateFactory {
42 
43     private static final String LOG_TAG = NitzStateMachineImpl.LOG_TAG;
44     private static final boolean DBG = NitzStateMachineImpl.DBG;
45     private static final String WAKELOCK_TAG = "NitzSignalInputFilterPredicateFactory";
46 
NitzSignalInputFilterPredicateFactory()47     private NitzSignalInputFilterPredicateFactory() {}
48 
49     /**
50      * Returns the real {@link NitzSignalInputFilterPredicate} to use for NITZ signal input
51      * filtering.
52      */
53     @NonNull
create( @onNull Context context, @NonNull DeviceState deviceState)54     public static NitzSignalInputFilterPredicate create(
55             @NonNull Context context, @NonNull DeviceState deviceState) {
56         Objects.requireNonNull(context);
57         Objects.requireNonNull(deviceState);
58 
59         TrivalentPredicate[] components = new TrivalentPredicate[] {
60                 // Disables NITZ processing entirely: can return false or null.
61                 createIgnoreNitzPropertyCheck(deviceState),
62                 // Filters bad reference times from new signals: can return false or null.
63                 createBogusElapsedRealtimeCheck(context, deviceState),
64                 // Ensures oldSignal == null is always processed: can return true or null.
65                 createNoOldSignalCheck(),
66                 // Adds rate limiting: can return true or false.
67                 createRateLimitCheck(deviceState),
68         };
69         return new NitzSignalInputFilterPredicateImpl(components);
70     }
71 
72     /**
73      * A filtering function that can give a {@code true} (must process), {@code false} (must not
74      * process) and a {@code null} (no opinion) response given a previous NITZ signal and a new
75      * signal. The previous signal may be {@code null} (unless ruled out by a prior
76      * {@link TrivalentPredicate}).
77      */
78     @VisibleForTesting
79     @FunctionalInterface
80     public interface TrivalentPredicate {
81 
82         /**
83          * See {@link TrivalentPredicate}.
84          */
85         @Nullable
mustProcessNitzSignal( @ullable TimestampedValue<NitzData> previousSignal, @NonNull TimestampedValue<NitzData> newSignal)86         Boolean mustProcessNitzSignal(
87                 @Nullable TimestampedValue<NitzData> previousSignal,
88                 @NonNull TimestampedValue<NitzData> newSignal);
89     }
90 
91     /**
92      * Returns a {@link TrivalentPredicate} function that implements a check for the
93      * "gsm.ignore-nitz" Android system property. The function can return {@code false} or
94      * {@code null}.
95      */
96     @VisibleForTesting
97     @NonNull
createIgnoreNitzPropertyCheck( @onNull DeviceState deviceState)98     public static TrivalentPredicate createIgnoreNitzPropertyCheck(
99             @NonNull DeviceState deviceState) {
100         return (oldSignal, newSignal) -> {
101             boolean ignoreNitz = deviceState.getIgnoreNitz();
102             if (ignoreNitz) {
103                 if (DBG) {
104                     Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal because"
105                             + " gsm.ignore-nitz is set");
106                 }
107                 return false;
108             }
109             return null;
110         };
111     }
112 
113     /**
114      * Returns a {@link TrivalentPredicate} function that implements a check for a bad reference
115      * time associated with {@code newSignal}. The function can return {@code false} or
116      * {@code null}.
117      */
118     @VisibleForTesting
119     @NonNull
createBogusElapsedRealtimeCheck( @onNull Context context, @NonNull DeviceState deviceState)120     public static TrivalentPredicate createBogusElapsedRealtimeCheck(
121             @NonNull Context context, @NonNull DeviceState deviceState) {
122         PowerManager powerManager =
123                 (PowerManager) context.getSystemService(Context.POWER_SERVICE);
124         final WakeLock wakeLock =
125                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
126 
127         return (oldSignal, newSignal) -> {
128             Objects.requireNonNull(newSignal);
129 
130             // Validate the newSignal to reject obviously bogus elapsedRealtime values.
131             try {
132                 // Acquire the wake lock as we are reading the elapsed realtime clock below.
133                 wakeLock.acquire();
134 
135                 long elapsedRealtime = deviceState.elapsedRealtime();
136                 long millisSinceNitzReceived = elapsedRealtime - newSignal.getReferenceTimeMillis();
137                 if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
138                     if (DBG) {
139                         Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal"
140                                 + " because unexpected elapsedRealtime=" + elapsedRealtime
141                                 + " nitzSignal=" + newSignal);
142                     }
143                     return false;
144                 }
145                 return null;
146             } finally {
147                 wakeLock.release();
148             }
149         };
150     }
151 
152     /**
153      * Returns a {@link TrivalentPredicate} function that implements a check for a {@code null}
154      * {@code oldSignal} (indicating there's no history). The function can return {@code true}
155      * or {@code null}.
156      */
157     @VisibleForTesting
158     @NonNull
159     public static TrivalentPredicate createNoOldSignalCheck() {
160         // Always process a signal when there was no previous signal.
161         return (oldSignal, newSignal) -> oldSignal == null ? true : null;
162     }
163 
164     /**
165      * Returns a {@link TrivalentPredicate} function that implements filtering using
166      * {@code oldSignal} and {@code newSignal}. The function can return {@code true} or
167      * {@code false} and so is intended as the final function in a chain.
168      *
169      * Function detail: if an NITZ signal received that is too similar to a previous one
170      * it should be disregarded if it's received within a configured time period.
171      * The general contract for {@link TrivalentPredicate} allows {@code previousSignal} to be
172      * {@code null}, but previous functions are expected to prevent it in this case.
173      */
174     @VisibleForTesting
175     @NonNull
176     public static TrivalentPredicate createRateLimitCheck(@NonNull DeviceState deviceState) {
177         return new TrivalentPredicate() {
178             @Override
179             @NonNull
180             public Boolean mustProcessNitzSignal(
181                     @NonNull TimestampedValue<NitzData> previousSignal,
182                     @NonNull TimestampedValue<NitzData> newSignal) {
183                 Objects.requireNonNull(newSignal);
184                 Objects.requireNonNull(newSignal.getValue());
185                 Objects.requireNonNull(previousSignal);
186                 Objects.requireNonNull(previousSignal.getValue());
187 
188                 NitzData newNitzData = newSignal.getValue();
189                 NitzData previousNitzData = previousSignal.getValue();
190 
191                 // Compare the discrete NitzData fields associated with local time offset. Any
192                 // difference and we should process the signal regardless of how recent the last one
193                 // was.
194                 if (!offsetInfoIsTheSame(previousNitzData, newNitzData)) {
195                     return true;
196                 }
197 
198                 // Now check the continuous NitzData field (time) to see if it is sufficiently
199                 // different.
200                 int nitzUpdateSpacing = deviceState.getNitzUpdateSpacingMillis();
201                 int nitzUpdateDiff = deviceState.getNitzUpdateDiffMillis();
202 
203                 // Calculate the elapsed time between the new signal and the last signal.
204                 long elapsedRealtimeSinceLastSaved = newSignal.getReferenceTimeMillis()
205                         - previousSignal.getReferenceTimeMillis();
206 
207                 // Calculate the UTC difference between the time the two signals hold.
208                 long utcTimeDifferenceMillis = newNitzData.getCurrentTimeInMillis()
209                         - previousNitzData.getCurrentTimeInMillis();
210 
211                 // Ideally the difference between elapsedRealtimeSinceLastSaved and
212                 // utcTimeDifferenceMillis would be zero.
213                 long millisGainedOrLost = Math
214                         .abs(utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved);
215 
216                 if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
217                         || millisGainedOrLost > nitzUpdateDiff) {
218                     return true;
219                 }
220 
221                 if (DBG) {
222                     Rlog.d(LOG_TAG, "mustProcessNitzSignal: NITZ signal filtered"
223                             + " previousSignal=" + previousSignal
224                             + ", newSignal=" + newSignal
225                             + ", nitzUpdateSpacing=" + nitzUpdateSpacing
226                             + ", nitzUpdateDiff=" + nitzUpdateDiff);
227                 }
228                 return false;
229             }
230 
231             private boolean offsetInfoIsTheSame(NitzData one, NitzData two) {
232                 return Objects.equals(two.getDstAdjustmentMillis(), one.getDstAdjustmentMillis())
233                         && Objects.equals(
234                                 two.getEmulatorHostTimeZone(), one.getEmulatorHostTimeZone())
235                         && two.getLocalOffsetMillis() == one.getLocalOffsetMillis();
236             }
237         };
238     }
239 
240     /**
241      * An implementation of {@link NitzSignalInputFilterPredicate} that tries a series of
242      * {@link TrivalentPredicate} instances until one provides a {@code true} or {@code false}
243      * response indicating that the {@code newSignal} should be processed or not. If all return
244      * {@code null} then a default of {@code true} is returned.
245      */
246     @VisibleForTesting
247     public static class NitzSignalInputFilterPredicateImpl
248             implements NitzSignalInputFilterPredicate {
249 
250         @NonNull
251         private final TrivalentPredicate[] mComponents;
252 
253         @VisibleForTesting
254         public NitzSignalInputFilterPredicateImpl(@NonNull TrivalentPredicate[] components) {
255             this.mComponents = Arrays.copyOf(components, components.length);
256         }
257 
258         @Override
259         public boolean mustProcessNitzSignal(@Nullable TimestampedValue<NitzData> oldSignal,
260                 @NonNull TimestampedValue<NitzData> newSignal) {
261             Objects.requireNonNull(newSignal);
262 
263             for (TrivalentPredicate component : mComponents) {
264                 Boolean result = component.mustProcessNitzSignal(oldSignal, newSignal);
265                 if (result != null) {
266                     return result;
267                 }
268             }
269             // The default is to process.
270             return true;
271         }
272     }
273 }
274