• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.telephony.NitzData;
27 import com.android.internal.telephony.NitzSignal;
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 NitzSignal previousSignal, @NonNull NitzSignal newSignal)86         Boolean mustProcessNitzSignal(
87                 @Nullable NitzSignal previousSignal,
88                 @NonNull NitzSignal 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.elapsedRealtimeMillis();
136                 long millisSinceNitzReceived =
137                         elapsedRealtime - newSignal.getReceiptElapsedRealtimeMillis();
138                 if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
139                     if (DBG) {
140                         Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal"
141                                 + " because unexpected elapsedRealtime=" + elapsedRealtime
142                                 + " nitzSignal=" + newSignal);
143                     }
144                     return false;
145                 }
146                 return null;
147             } finally {
148                 wakeLock.release();
149             }
150         };
151     }
152 
153     /**
154      * Returns a {@link TrivalentPredicate} function that implements a check for a {@code null}
155      * {@code oldSignal} (indicating there's no history). The function can return {@code true}
156      * or {@code null}.
157      */
158     @VisibleForTesting
159     @NonNull
160     public static TrivalentPredicate createNoOldSignalCheck() {
161         // Always process a signal when there was no previous signal.
162         return (oldSignal, newSignal) -> oldSignal == null ? true : null;
163     }
164 
165     /**
166      * Returns a {@link TrivalentPredicate} function that implements filtering using
167      * {@code oldSignal} and {@code newSignal}. The function can return {@code true} or
168      * {@code false} and so is intended as the final function in a chain.
169      *
170      * Function detail: if an NITZ signal received that is too similar to a previous one
171      * it should be disregarded if it's received within a configured time period.
172      * The general contract for {@link TrivalentPredicate} allows {@code previousSignal} to be
173      * {@code null}, but previous functions are expected to prevent it in this case.
174      */
175     @VisibleForTesting
176     @NonNull
177     public static TrivalentPredicate createRateLimitCheck(@NonNull DeviceState deviceState) {
178         return new TrivalentPredicate() {
179             @Override
180             @NonNull
181             public Boolean mustProcessNitzSignal(
182                     @NonNull NitzSignal previousSignal,
183                     @NonNull NitzSignal newSignal) {
184                 Objects.requireNonNull(newSignal);
185                 Objects.requireNonNull(newSignal.getNitzData());
186                 Objects.requireNonNull(previousSignal);
187                 Objects.requireNonNull(previousSignal.getNitzData());
188 
189                 NitzData newNitzData = newSignal.getNitzData();
190                 NitzData previousNitzData = previousSignal.getNitzData();
191 
192                 // Compare the discrete NitzData fields associated with local time offset. Any
193                 // difference and we should process the signal regardless of how recent the last one
194                 // was.
195                 if (!offsetInfoIsTheSame(previousNitzData, newNitzData)) {
196                     return true;
197                 }
198 
199                 // Check the time-related NitzData fields to see if they are sufficiently different.
200 
201                 // See if the NITZ signals have been received sufficiently far apart. If yes, we
202                 // want to process the new one.
203                 int nitzUpdateSpacing = deviceState.getNitzUpdateSpacingMillis();
204                 long elapsedRealtimeSinceLastSaved = newSignal.getReceiptElapsedRealtimeMillis()
205                         - previousSignal.getReceiptElapsedRealtimeMillis();
206                 if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing) {
207                     return true;
208                 }
209 
210                 // See if the NITZ signals have sufficiently different encoded Unix epoch times. If
211                 // yes, then we want to process the new one.
212                 int nitzUpdateDiff = deviceState.getNitzUpdateDiffMillis();
213 
214                 // Calculate the Unix epoch difference between the time the two signals hold,
215                 // accounting for any difference in receipt time and age.
216                 long unixEpochTimeDifferenceMillis = newNitzData.getCurrentTimeInMillis()
217                         - previousNitzData.getCurrentTimeInMillis();
218                 long ageAdjustedElapsedRealtimeDifferenceMillis =
219                         newSignal.getAgeAdjustedElapsedRealtimeMillis()
220                                 - previousSignal.getAgeAdjustedElapsedRealtimeMillis();
221 
222                 // In ideal conditions, the difference between
223                 // ageAdjustedElapsedRealtimeSinceLastSaved and unixEpochTimeDifferenceMillis will
224                 // be zero if two NITZ signals are consistent and if the elapsed realtime clock is
225                 // ticking at the correct rate.
226                 long millisGainedOrLost = Math.abs(
227                         unixEpochTimeDifferenceMillis - ageAdjustedElapsedRealtimeDifferenceMillis);
228                 if (millisGainedOrLost > nitzUpdateDiff) {
229                     return true;
230                 }
231 
232                 if (DBG) {
233                     Rlog.d(LOG_TAG, "mustProcessNitzSignal: NITZ signal filtered"
234                             + " previousSignal=" + previousSignal
235                             + ", newSignal=" + newSignal
236                             + ", nitzUpdateSpacing=" + nitzUpdateSpacing
237                             + ", nitzUpdateDiff=" + nitzUpdateDiff);
238                 }
239                 return false;
240             }
241 
242             private boolean offsetInfoIsTheSame(NitzData one, NitzData two) {
243                 return Objects.equals(two.getDstAdjustmentMillis(), one.getDstAdjustmentMillis())
244                         && Objects.equals(
245                                 two.getEmulatorHostTimeZone(), one.getEmulatorHostTimeZone())
246                         && two.getLocalOffsetMillis() == one.getLocalOffsetMillis();
247             }
248         };
249     }
250 
251     /**
252      * An implementation of {@link NitzSignalInputFilterPredicate} that tries a series of
253      * {@link TrivalentPredicate} instances until one provides a {@code true} or {@code false}
254      * response indicating that the {@code newSignal} should be processed or not. If all return
255      * {@code null} then a default of {@code true} is returned.
256      */
257     @VisibleForTesting
258     public static class NitzSignalInputFilterPredicateImpl
259             implements NitzSignalInputFilterPredicate {
260 
261         @NonNull
262         private final TrivalentPredicate[] mComponents;
263 
264         @VisibleForTesting
265         public NitzSignalInputFilterPredicateImpl(@NonNull TrivalentPredicate[] components) {
266             this.mComponents = Arrays.copyOf(components, components.length);
267         }
268 
269         @Override
270         public boolean mustProcessNitzSignal(@Nullable NitzSignal oldSignal,
271                 @NonNull NitzSignal newSignal) {
272             Objects.requireNonNull(newSignal);
273 
274             for (TrivalentPredicate component : mComponents) {
275                 Boolean result = component.mustProcessNitzSignal(oldSignal, newSignal);
276                 if (result != null) {
277                     return result;
278                 }
279             }
280             // The default is to process.
281             return true;
282         }
283     }
284 }
285