1 /*
2  * Copyright (C) 2016 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 package com.android.contacts.model;
17 
18 import android.os.Build;
19 import androidx.annotation.RequiresApi;
20 import android.telephony.PhoneNumberUtils;
21 import android.telephony.SubscriptionInfo;
22 import android.telephony.TelephonyManager;
23 import android.util.Log;
24 
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Objects;
30 
31 /**
32  * Holds data for a SIM card in the device.
33  */
34 public class SimCard {
35 
36     private static final String TAG = "SimCard";
37 
38     public static final int NO_SUBSCRIPTION_ID = -1;
39 
40     // This state is created from the info we get from the system
41     private final String mSimId;
42     private final int mSubscriptionId;
43     private final CharSequence mCarrierName;
44     private final CharSequence mDisplayName;
45     private final String mPhoneNumber;
46     private final String mCountryCode;
47 
48     // This is our own state that we associate with SIM cards. Currently these are only used
49     // in the GoogleContacts app.
50     // Note: these are logically immutable but are not final to reduce required constructor
51     // parameters
52     private boolean mDismissed = false;
53     private boolean mImported = false;
54 
55     private List<SimContact> mContacts;
56 
SimCard(SimCard other)57     public SimCard(SimCard other) {
58         mSimId = other.mSimId;
59         mSubscriptionId = other.mSubscriptionId;
60         mCarrierName = other.mCarrierName;
61         mDisplayName = other.mDisplayName;
62         mPhoneNumber = other.mPhoneNumber;
63         mCountryCode = other.mCountryCode;
64         mDismissed = other.mDismissed;
65         mImported = other.mImported;
66         if (other.mContacts != null) {
67             mContacts = new ArrayList<>(other.mContacts);
68         }
69     }
70 
SimCard(String simId, int subscriptionId, CharSequence carrierName, CharSequence displayName, String phoneNumber, String countryCode)71     public SimCard(String simId, int subscriptionId, CharSequence carrierName,
72             CharSequence displayName, String phoneNumber, String countryCode) {
73         mSimId = simId;
74         mSubscriptionId = subscriptionId;
75         mCarrierName = carrierName;
76         mDisplayName = displayName;
77         mPhoneNumber = phoneNumber;
78         mCountryCode = countryCode != null ? countryCode.toUpperCase(Locale.US) : null;
79     }
80 
SimCard(String simId, CharSequence carrierName, CharSequence displayName, String phoneNumber, String countryCode)81     public SimCard(String simId, CharSequence carrierName,
82             CharSequence displayName, String phoneNumber, String countryCode) {
83         this(simId, NO_SUBSCRIPTION_ID, carrierName, displayName, phoneNumber, countryCode);
84     }
85 
getSimId()86     public String getSimId() {
87         return mSimId;
88     }
89 
getSubscriptionId()90     public int getSubscriptionId() {
91         return mSubscriptionId;
92     }
93 
hasValidSubscriptionId()94     public boolean hasValidSubscriptionId() {
95         return mSubscriptionId != NO_SUBSCRIPTION_ID;
96     }
97 
getDisplayName()98     public CharSequence getDisplayName() {
99         return mDisplayName;
100     }
101 
getPhone()102     public String getPhone() {
103         return mPhoneNumber;
104     }
105 
getFormattedPhone()106     public CharSequence getFormattedPhone() {
107         if (mPhoneNumber == null) {
108             return null;
109         }
110         return PhoneNumberUtils.formatNumber(mPhoneNumber, mCountryCode);
111     }
112 
hasPhone()113     public boolean hasPhone() {
114         return mPhoneNumber != null;
115     }
116 
getCountryCode()117     public String getCountryCode() {
118         return mCountryCode;
119     }
120 
121     /**
122      * Returns whether the contacts for this SIM card have been initialized.
123      */
areContactsAvailable()124     public boolean areContactsAvailable() {
125         return mContacts != null;
126     }
127 
128     /**
129      * Returns whether this SIM card has any SIM contacts.
130      *
131      * A precondition of this method is that the contacts have been initialized.
132      */
hasContacts()133     public boolean hasContacts() {
134         if (mContacts == null) {
135             throw new IllegalStateException("Contacts not loaded.");
136         }
137         return !mContacts.isEmpty();
138     }
139 
140     /**
141      * Returns the number of contacts stored on this SIM card.
142      *
143      * A precondition of this method is that the contacts have been initialized.
144      */
getContactCount()145     public int getContactCount() {
146         if (mContacts == null) {
147             throw new IllegalStateException("Contacts not loaded.");
148         }
149         return mContacts.size();
150     }
151 
isDismissed()152     public boolean isDismissed() {
153         return mDismissed;
154     }
155 
isImported()156     public boolean isImported() {
157         return mImported;
158     }
159 
isImportable()160     public boolean isImportable() {
161         if (Log.isLoggable(TAG, Log.DEBUG)) {
162             Log.d(TAG, "isImportable: isDismissed? " + isDismissed() +
163                     " isImported? " + isImported() + " contacts=" + mContacts);
164         }
165         return !isDismissed() && !isImported() && hasContacts();
166     }
167 
168     /**
169      * Returns the contacts for this SIM card or null if the contacts have not been initialized.
170      */
getContacts()171     public List<SimContact> getContacts() {
172         return mContacts;
173     }
174 
withImportAndDismissStates(boolean imported, boolean dismissed)175     public SimCard withImportAndDismissStates(boolean imported, boolean dismissed) {
176         SimCard copy = new SimCard(this);
177         copy.mImported = imported;
178         copy.mDismissed = dismissed;
179         return copy;
180     }
181 
withImportedState(boolean imported)182     public SimCard withImportedState(boolean imported) {
183         return withImportAndDismissStates(imported, mDismissed);
184     }
185 
withDismissedState(boolean dismissed)186     public SimCard withDismissedState(boolean dismissed) {
187         return withImportAndDismissStates(mImported, dismissed);
188     }
189 
withContacts(List<SimContact> contacts)190     public SimCard withContacts(List<SimContact> contacts) {
191         final SimCard copy = new SimCard(this);
192         copy.mContacts = contacts;
193         return copy;
194     }
195 
withContacts(SimContact... contacts)196     public SimCard withContacts(SimContact... contacts) {
197         final SimCard copy = new SimCard(this);
198         copy.mContacts = Arrays.asList(contacts);
199         return copy;
200     }
201 
202     @Override
equals(Object o)203     public boolean equals(Object o) {
204         if (this == o) return true;
205         if (o == null || getClass() != o.getClass()) return false;
206 
207         SimCard simCard = (SimCard) o;
208 
209         return mSubscriptionId == simCard.mSubscriptionId && mDismissed == simCard.mDismissed &&
210                 mImported == simCard.mImported && Objects.equals(mSimId, simCard.mSimId) &&
211                 Objects.equals(mPhoneNumber, simCard.mPhoneNumber) &&
212                 Objects.equals(mCountryCode, simCard.mCountryCode);
213     }
214 
215     @Override
hashCode()216     public int hashCode() {
217         int result = Objects.hash(mSimId, mPhoneNumber, mCountryCode);
218         result = 31 * result + mSubscriptionId;
219         result = 31 * result + (mDismissed ? 1 : 0);
220         result = 31 * result + (mImported ? 1 : 0);
221         return result;
222     }
223 
224     @Override
toString()225     public String toString() {
226         return "SimCard{" +
227                 "mSimId='" + mSimId + '\'' +
228                 ", mSubscriptionId=" + mSubscriptionId +
229                 ", mCarrierName=" + mCarrierName +
230                 ", mDisplayName=" + mDisplayName +
231                 ", mPhoneNumber='" + mPhoneNumber + '\'' +
232                 ", mCountryCode='" + mCountryCode + '\'' +
233                 ", mDismissed=" + mDismissed +
234                 ", mImported=" + mImported +
235                 ", mContacts=" + mContacts +
236                 '}';
237     }
238 
239     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
create(SubscriptionInfo info)240     public static SimCard create(SubscriptionInfo info) {
241         return new SimCard(info.getIccId(), info.getSubscriptionId(),
242                 info.getCarrierName(), info.getDisplayName(), info.getNumber(),
243                 info.getCountryIso());
244     }
245 
create(TelephonyManager telephony, String displayLabel)246     public static SimCard create(TelephonyManager telephony, String displayLabel) {
247         if (telephony.getSimState() == TelephonyManager.SIM_STATE_READY) {
248             return new SimCard(telephony.getSimSerialNumber(),
249                     telephony.getSimOperatorName(), displayLabel, telephony.getLine1Number(),
250                     telephony.getSimCountryIso());
251         } else {
252             // This should never happen but in case it does just fallback to an "empty" instance
253             return new SimCard(/* SIM id */ "", /* operator name */ null, displayLabel,
254                     /* phone number */ "", /* Country code */ null);
255         }
256     }
257 }
258