1 /*
2  * Copyright 2017 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 android.telephony;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.hardware.radio.V1_0.CellInfoType;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.text.TextUtils;
27 
28 import com.android.telephony.Rlog;
29 
30 import java.util.Objects;
31 import java.util.UUID;
32 
33 /**
34  * CellIdentity represents the identity of a unique cell. This is the base class for
35  * CellIdentityXxx which represents cell identity for specific network access technology.
36  */
37 public abstract class CellIdentity implements Parcelable {
38 
39     /** @hide */
40     public static final int INVALID_CHANNEL_NUMBER = -1;
41 
42     /**
43      * parameters for validation
44      * @hide
45      */
46     public static final int MCC_LENGTH = 3;
47 
48     /** @hide */
49     public static final int MNC_MIN_LENGTH = 2;
50     /** @hide */
51     public static final int MNC_MAX_LENGTH = 3;
52 
53     // Log tag
54     /** @hide */
55     protected final String mTag;
56     // Cell identity type
57     /** @hide */
58     protected final int mType;
59     // 3-digit Mobile Country Code in string format. Null for CDMA cell identity.
60     /** @hide */
61     protected final String mMccStr;
62     // 2 or 3-digit Mobile Network Code in string format. Null for CDMA cell identity.
63     /** @hide */
64     protected final String mMncStr;
65 
66     // long alpha Operator Name String or Enhanced Operator Name String
67     /** @hide */
68     protected String mAlphaLong;
69     // short alpha Operator Name String or Enhanced Operator Name String
70     /** @hide */
71     protected String mAlphaShort;
72 
73     // For GSM, WCDMA, TDSCDMA, LTE and NR, Cell Global ID is defined in 3GPP TS 23.003.
74     // For CDMA, its defined as System Id + Network Id + Basestation Id.
75     /** @hide */
76     protected String mGlobalCellId;
77 
78 
79     /** @hide */
CellIdentity(@ullable String tag, int type, @Nullable String mcc, @Nullable String mnc, @Nullable String alphal, @Nullable String alphas)80     protected CellIdentity(@Nullable String tag, int type, @Nullable String mcc,
81             @Nullable String mnc, @Nullable String alphal, @Nullable String alphas) {
82         mTag = tag;
83         mType = type;
84 
85         // Only allow INT_MAX if unknown string mcc/mnc
86         if (mcc == null || isMcc(mcc)) {
87             mMccStr = mcc;
88         } else if (mcc.isEmpty() || mcc.equals(String.valueOf(Integer.MAX_VALUE))) {
89             // If the mccStr is empty or unknown, set it as null.
90             mMccStr = null;
91         } else {
92             // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format
93             // after the bug got fixed.
94             mMccStr = null;
95             log("invalid MCC format: " + mcc);
96         }
97 
98         if (mnc == null || isMnc(mnc)) {
99             mMncStr = mnc;
100         } else if (mnc.isEmpty() || mnc.equals(String.valueOf(Integer.MAX_VALUE))) {
101             // If the mncStr is empty or unknown, set it as null.
102             mMncStr = null;
103         } else {
104             // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format
105             // after the bug got fixed.
106             mMncStr = null;
107             log("invalid MNC format: " + mnc);
108         }
109 
110         if ((mMccStr != null && mMncStr == null) || (mMccStr == null && mMncStr != null)) {
111             AnomalyReporter.reportAnomaly(
112                     UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
113                     "CellIdentity Missing Half of PLMN ID");
114         }
115 
116         mAlphaLong = alphal;
117         mAlphaShort = alphas;
118     }
119 
120     /** Implement the Parcelable interface */
121     @Override
describeContents()122     public int describeContents() {
123         return 0;
124     }
125 
126     /**
127      * @hide
128      * @return The type of the cell identity
129      */
getType()130     public @CellInfo.Type int getType() {
131         return mType;
132     }
133 
134     /**
135      * @return MCC or null for CDMA
136      * @hide
137      */
getMccString()138     public String getMccString() {
139         return mMccStr;
140     }
141 
142     /**
143      * @return MNC or null for CDMA
144      * @hide
145      */
getMncString()146     public String getMncString() {
147         return mMncStr;
148     }
149 
150     /**
151      * Returns the channel number of the cell identity.
152      *
153      * @hide
154      * @return The channel number, or {@link #INVALID_CHANNEL_NUMBER} if not implemented
155      */
getChannelNumber()156     public int getChannelNumber() {
157         return INVALID_CHANNEL_NUMBER;
158     }
159 
160     /**
161      * @return The long alpha tag associated with the current scan result (may be the operator
162      * name string or extended operator name string). May be null if unknown.
163      */
164     @Nullable
getOperatorAlphaLong()165     public CharSequence getOperatorAlphaLong() {
166         return mAlphaLong;
167     }
168 
169     /**
170      * @hide
171      */
setOperatorAlphaLong(String alphaLong)172     public void setOperatorAlphaLong(String alphaLong) {
173         mAlphaLong = alphaLong;
174     }
175 
176     /**
177      * @return The short alpha tag associated with the current scan result (may be the operator
178      * name string or extended operator name string).  May be null if unknown.
179      */
180     @Nullable
getOperatorAlphaShort()181     public CharSequence getOperatorAlphaShort() {
182         return mAlphaShort;
183     }
184 
185     /**
186      * @hide
187      */
setOperatorAlphaShort(String alphaShort)188     public void setOperatorAlphaShort(String alphaShort) {
189         mAlphaShort = alphaShort;
190     }
191 
192     /**
193      * @return Global Cell ID
194      * @hide
195      */
196     @Nullable
getGlobalCellId()197     public String getGlobalCellId() {
198         return mGlobalCellId;
199     }
200 
201     /**
202      * @param ci a CellIdentity to compare to the current CellIdentity.
203      * @return true if ci has the same technology and Global Cell ID; false, otherwise.
204      * @hide
205      */
isSameCell(@ullable CellIdentity ci)206     public boolean isSameCell(@Nullable CellIdentity ci) {
207         if (ci == null) return false;
208         if (this.getClass() != ci.getClass()) return false;
209         if (this.getGlobalCellId() == null || ci.getGlobalCellId() == null) return false;
210         return TextUtils.equals(this.getGlobalCellId(), ci.getGlobalCellId());
211     }
212 
213     /** @hide */
getPlmn()214     public @Nullable String getPlmn() {
215         if (mMccStr == null || mMncStr == null) return null;
216         return mMccStr + mMncStr;
217     }
218 
219     /** @hide */
updateGlobalCellId()220     protected abstract void updateGlobalCellId();
221 
222     /**
223      * @return a CellLocation object for this CellIdentity
224      * @hide
225      */
226     @SystemApi
asCellLocation()227     public abstract @NonNull CellLocation asCellLocation();
228 
229     /**
230      * Create and a return a new instance of CellIdentity with location-identifying information
231      * removed.
232      *
233      * @hide
234      */
235     @SystemApi
sanitizeLocationInfo()236     public abstract @NonNull CellIdentity sanitizeLocationInfo();
237 
238     @Override
equals(Object other)239     public boolean equals(Object other) {
240         if (!(other instanceof CellIdentity)) {
241             return false;
242         }
243 
244         CellIdentity o = (CellIdentity) other;
245         return mType == o.mType
246                 && TextUtils.equals(mMccStr, o.mMccStr)
247                 && TextUtils.equals(mMncStr, o.mMncStr)
248                 && TextUtils.equals(mAlphaLong, o.mAlphaLong)
249                 && TextUtils.equals(mAlphaShort, o.mAlphaShort);
250     }
251 
252     @Override
hashCode()253     public int hashCode() {
254         return Objects.hash(mAlphaLong, mAlphaShort, mMccStr, mMncStr, mType);
255     }
256 
257     /**
258      * Used by child classes for parceling.
259      *
260      * @hide
261      */
262     @CallSuper
writeToParcel(Parcel dest, int type)263     public void writeToParcel(Parcel dest, int type) {
264         dest.writeInt(type);
265         dest.writeString(mMccStr);
266         dest.writeString(mMncStr);
267         dest.writeString(mAlphaLong);
268         dest.writeString(mAlphaShort);
269     }
270 
271     /** Used by phone interface manager to verify if a given string is valid MccMnc
272      * @hide
273      */
isValidPlmn(@onNull String plmn)274     public static boolean isValidPlmn(@NonNull String plmn) {
275         if (plmn.length() < MCC_LENGTH + MNC_MIN_LENGTH
276                 || plmn.length() > MCC_LENGTH + MNC_MAX_LENGTH) {
277             return false;
278         }
279         return (isMcc(plmn.substring(0, MCC_LENGTH)) && isMnc(plmn.substring(MCC_LENGTH)));
280     }
281 
282     /**
283      * Construct from Parcel
284      * @hide
285      */
CellIdentity(String tag, int type, Parcel source)286     protected CellIdentity(String tag, int type, Parcel source) {
287         this(tag, type, source.readString(), source.readString(),
288                 source.readString(), source.readString());
289     }
290 
291     /** Implement the Parcelable interface */
292     public static final @android.annotation.NonNull Creator<CellIdentity> CREATOR =
293             new Creator<CellIdentity>() {
294                 @Override
295                 public CellIdentity createFromParcel(Parcel in) {
296                     int type = in.readInt();
297                     switch (type) {
298                         case CellInfo.TYPE_GSM: return CellIdentityGsm.createFromParcelBody(in);
299                         case CellInfo.TYPE_WCDMA: return CellIdentityWcdma.createFromParcelBody(in);
300                         case CellInfo.TYPE_CDMA: return CellIdentityCdma.createFromParcelBody(in);
301                         case CellInfo.TYPE_LTE: return CellIdentityLte.createFromParcelBody(in);
302                         case CellInfo.TYPE_TDSCDMA:
303                             return CellIdentityTdscdma.createFromParcelBody(in);
304                         case CellInfo.TYPE_NR: return CellIdentityNr.createFromParcelBody(in);
305                         default: throw new IllegalArgumentException("Bad Cell identity Parcel");
306                     }
307                 }
308 
309                 @Override
310                 public CellIdentity[] newArray(int size) {
311                     return new CellIdentity[size];
312                 }
313             };
314 
315     /** @hide */
log(String s)316     protected void log(String s) {
317         Rlog.w(mTag, s);
318     }
319 
320     /** @hide */
inRangeOrUnavailable(int value, int rangeMin, int rangeMax)321     protected static final int inRangeOrUnavailable(int value, int rangeMin, int rangeMax) {
322         if (value < rangeMin || value > rangeMax) return CellInfo.UNAVAILABLE;
323         return value;
324     }
325 
326     /** @hide */
inRangeOrUnavailable(long value, long rangeMin, long rangeMax)327     protected static final long inRangeOrUnavailable(long value, long rangeMin, long rangeMax) {
328         if (value < rangeMin || value > rangeMax) return CellInfo.UNAVAILABLE_LONG;
329         return value;
330     }
331 
332     /** @hide */
inRangeOrUnavailable( int value, int rangeMin, int rangeMax, int special)333     protected static final int inRangeOrUnavailable(
334             int value, int rangeMin, int rangeMax, int special) {
335         if ((value < rangeMin || value > rangeMax) && value != special) return CellInfo.UNAVAILABLE;
336         return value;
337     }
338 
339     /** @hide */
isMcc(@onNull String mcc)340     private static boolean isMcc(@NonNull String mcc) {
341         // ensure no out of bounds indexing
342         if (mcc.length() != MCC_LENGTH) return false;
343 
344         // Character.isDigit allows all unicode digits, not just [0-9]
345         for (int i = 0; i < MCC_LENGTH; i++) {
346             if (mcc.charAt(i) < '0' || mcc.charAt(i) > '9') return false;
347         }
348 
349         return true;
350     }
351 
352     /** @hide */
isMnc(@onNull String mnc)353     private static boolean isMnc(@NonNull String mnc) {
354         // ensure no out of bounds indexing
355         if (mnc.length() < MNC_MIN_LENGTH || mnc.length() > MNC_MAX_LENGTH) return false;
356 
357         // Character.isDigit allows all unicode digits, not just [0-9]
358         for (int i = 0; i < mnc.length(); i++) {
359             if (mnc.charAt(i) < '0' || mnc.charAt(i) > '9') return false;
360         }
361 
362         return true;
363     }
364 
365     /** @hide */
create(android.hardware.radio.V1_0.CellIdentity cellIdentity)366     public static CellIdentity create(android.hardware.radio.V1_0.CellIdentity cellIdentity) {
367         if (cellIdentity == null)  return null;
368         switch(cellIdentity.cellInfoType) {
369             case CellInfoType.GSM: {
370                 if (cellIdentity.cellIdentityGsm.size() == 1) {
371                     return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
372                 }
373                 break;
374             }
375             case CellInfoType.WCDMA: {
376                 if (cellIdentity.cellIdentityWcdma.size() == 1) {
377                     return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
378                 }
379                 break;
380             }
381             case CellInfoType.TD_SCDMA: {
382                 if (cellIdentity.cellIdentityTdscdma.size() == 1) {
383                     return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
384                 }
385                 break;
386             }
387             case CellInfoType.LTE: {
388                 if (cellIdentity.cellIdentityLte.size() == 1) {
389                     return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
390                 }
391                 break;
392             }
393             case CellInfoType.CDMA: {
394                 if (cellIdentity.cellIdentityCdma.size() == 1) {
395                     return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
396                 }
397                 break;
398             }
399             case CellInfoType.NONE: break;
400             default: break;
401         }
402         return null;
403     }
404 
405     /** @hide */
create(android.hardware.radio.V1_2.CellIdentity cellIdentity)406     public static CellIdentity create(android.hardware.radio.V1_2.CellIdentity cellIdentity) {
407         if (cellIdentity == null)  return null;
408         switch(cellIdentity.cellInfoType) {
409             case CellInfoType.GSM: {
410                 if (cellIdentity.cellIdentityGsm.size() == 1) {
411                     return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
412                 }
413                 break;
414             }
415             case CellInfoType.WCDMA: {
416                 if (cellIdentity.cellIdentityWcdma.size() == 1) {
417                     return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
418                 }
419                 break;
420             }
421             case CellInfoType.TD_SCDMA: {
422                 if (cellIdentity.cellIdentityTdscdma.size() == 1) {
423                     return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
424                 }
425                 break;
426             }
427             case CellInfoType.LTE: {
428                 if (cellIdentity.cellIdentityLte.size() == 1) {
429                     return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
430                 }
431                 break;
432             }
433             case CellInfoType.CDMA: {
434                 if (cellIdentity.cellIdentityCdma.size() == 1) {
435                     return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
436                 }
437                 break;
438             }
439             case CellInfoType.NONE: break;
440             default: break;
441         }
442         return null;
443     }
444 
445     /** @hide */
create(android.hardware.radio.V1_5.CellIdentity ci)446     public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) {
447         if (ci == null) return null;
448         switch (ci.getDiscriminator()) {
449             case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.gsm:
450                 return new CellIdentityGsm(ci.gsm());
451             case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.cdma:
452                 return new CellIdentityCdma(ci.cdma());
453             case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.lte:
454                 return new CellIdentityLte(ci.lte());
455             case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.wcdma:
456                 return new CellIdentityWcdma(ci.wcdma());
457             case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.tdscdma:
458                 return new CellIdentityTdscdma(ci.tdscdma());
459             case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.nr:
460                 return new CellIdentityNr(ci.nr());
461             default: return null;
462         }
463     }
464 }
465